mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-03 13:46:33 +02:00
Compare commits
104 Commits
console_cm
...
mdns-v1.5.
Author | SHA1 | Date | |
---|---|---|---|
936e43f9d8 | |||
64d818b2d3 | |||
cd07228f81 | |||
1c6580e22b | |||
87f835af0f | |||
85a8dac42d | |||
39866116f5 | |||
7740b591b6 | |||
b57979d967 | |||
42674b49f9 | |||
e069ae7762 | |||
44d476fc50 | |||
55385ec312 | |||
a3c2bbed9e | |||
84c47c37f1 | |||
e7273c46ec | |||
2e7d240abd | |||
d4a004b5b4 | |||
9046af8f8d | |||
eeeb9006eb | |||
b167aa315f | |||
72ba24470d | |||
7dc87d28b2 | |||
ac6a388cdd | |||
76aaea08d2 | |||
5db32cce30 | |||
9bdd429c7c | |||
6f7c52cc3f | |||
27435b7f34 | |||
813331f003 | |||
bd23c233a4 | |||
4eda7d472f | |||
163029c0b6 | |||
96eae25096 | |||
ebec8eff63 | |||
4451a8c5ad | |||
6d19aabb02 | |||
9162de1150 | |||
dbd164dd91 | |||
90d663ad01 | |||
a83f1b6787 | |||
84caca465d | |||
8f81478fff | |||
ae5a8ceeda | |||
774bab22ea | |||
265e38d684 | |||
93f772171c | |||
4ad88e297f | |||
196198ecc9 | |||
e838bf03f4 | |||
99b54ac384 | |||
f5be2f4115 | |||
9b74256b51 | |||
3d8835cfb9 | |||
24f55ce9b4 | |||
8f8516cc3f | |||
75a8e8640a | |||
907087c09b | |||
68a9e14898 | |||
827ea65fd5 | |||
9537721600 | |||
4394f845fc | |||
9b0ba6060f | |||
7c6a3098af | |||
f3f3e23bec | |||
9ed835ba3f | |||
f12a205657 | |||
5bd82c01a5 | |||
b4cb8f8a66 | |||
0499ed93df | |||
ade9448c01 | |||
4745fc8fe1 | |||
73e523e736 | |||
3cd0ed377b | |||
95294f5f89 | |||
5dcc33300f | |||
840a561de4 | |||
e6fb8aa078 | |||
3b2c614d86 | |||
cdeab8f517 | |||
6cce87e465 | |||
d57b8c5b29 | |||
9c11003449 | |||
85a7fc772c | |||
b090a3cb69 | |||
42cde46c97 | |||
beb6e57e5e | |||
15d3a01e11 | |||
e12ecb8e89 | |||
54271a1b96 | |||
886215032f | |||
269351f41c | |||
5e929902c7 | |||
f7c0b7564a | |||
c989c6adae | |||
18f196fa1e | |||
1db83cd1ca | |||
247f1681e8 | |||
32387f7e39 | |||
dbc3ea6809 | |||
8b6ea3311a | |||
8e55b93b59 | |||
0cb59ff80d | |||
1284f66d58 |
@ -13,7 +13,7 @@ jobs:
|
||||
name: Build
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
|
||||
idf_target: ["esp32", "esp32s2"]
|
||||
example: ["asio_chat", "async_request", "socks4", "ssl_client_server", "tcp_echo_server", "udp_echo_server"]
|
||||
runs-on: ubuntu-22.04
|
||||
@ -64,7 +64,7 @@ jobs:
|
||||
name: Target tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest", "release-v5.0", "release-v5.2", "release-v5.3"]
|
||||
idf_ver: ["latest", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
|
||||
idf_target: ["esp32"]
|
||||
example: ["asio_chat", "tcp_echo_server", "udp_echo_server", "ssl_client_server"]
|
||||
needs: build_asio
|
||||
|
1
.github/workflows/clang-tidy.yml
vendored
1
.github/workflows/clang-tidy.yml
vendored
@ -24,6 +24,7 @@ jobs:
|
||||
chmod +x clang-tidy-sarif
|
||||
curl -sSL https://raw.githubusercontent.com/espressif/idf-extra-components/master/.github/filter_sarif.py -o filter_sarif.py
|
||||
- name: Install pyclang
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install pyclang~=0.2.0
|
||||
|
13
.github/workflows/modem__target-test.yml
vendored
13
.github/workflows/modem__target-test.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
||||
idf.py set-target ${{ matrix.idf_target }}
|
||||
idf.py build
|
||||
$GITHUB_WORKSPACE/ci/clean_build_artifacts.sh ${GITHUB_WORKSPACE}/${TEST_DIR}/build
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
|
||||
path: ${{ env.TEST_DIR }}/build
|
||||
@ -75,12 +75,17 @@ jobs:
|
||||
run: |
|
||||
sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: modem_target_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }}
|
||||
path: ${{ env.TEST_DIR }}/build
|
||||
- name: Run Example Test on target
|
||||
working-directory: ${{ env.TEST_DIR }}
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi/"
|
||||
run: |
|
||||
python -m pip install -r $GITHUB_WORKSPACE/ci/requirements.txt
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
|
||||
pip install -r $GITHUB_WORKSPACE/ci/requirements.txt
|
||||
cd ${{ env.TEST_DIR }}
|
||||
python -m pytest --log-cli-level DEBUG --target=${{ matrix.idf_target }}
|
||||
|
115
.github/workflows/mosq__build.yml
vendored
115
.github/workflows/mosq__build.yml
vendored
@ -17,7 +17,8 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
env:
|
||||
TEST_DIR: components/mosquitto/examples/broker
|
||||
TEST_DIR: components/mosquitto/examples
|
||||
TARGET_TEST: broker
|
||||
TARGET_TEST_DIR: build_esp32_default
|
||||
steps:
|
||||
- name: Checkout esp-protocols
|
||||
@ -29,14 +30,15 @@ jobs:
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install idf-component-manager idf-build-apps --upgrade
|
||||
python ci/build_apps.py ${TEST_DIR}
|
||||
cd ${TEST_DIR}
|
||||
python ci/build_apps.py -c ${TEST_DIR} -m components/mosquitto/.build-test-rules.yml
|
||||
# upload only the target test artifacts
|
||||
cd ${TEST_DIR}/${TARGET_TEST}
|
||||
${GITHUB_WORKSPACE}/ci/clean_build_artifacts.sh `pwd`/${TARGET_TEST_DIR}
|
||||
zip -qur artifacts.zip ${TARGET_TEST_DIR}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mosq_target_esp32_${{ matrix.idf_ver }}
|
||||
path: ${{ env.TEST_DIR }}/artifacts.zip
|
||||
path: ${{ env.TEST_DIR }}/${{ env.TARGET_TEST }}/artifacts.zip
|
||||
if-no-files-found: error
|
||||
|
||||
test_mosq:
|
||||
@ -71,3 +73,108 @@ jobs:
|
||||
mv $dir build
|
||||
python -m pytest --log-cli-level DEBUG --junit-xml=./results_esp32_${{ matrix.idf_ver }}_${dir#"ci/build_"}.xml --target=esp32
|
||||
done
|
||||
|
||||
check_consistency:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'mosquitto') || github.event_name == 'push'
|
||||
name: Checks that API docs and versions are consistent
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checks API Docs and versions
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install doxygen
|
||||
pip install esp-doxybook
|
||||
cd components/mosquitto
|
||||
cp api.md api_orig.md
|
||||
./generate_api_docs.sh
|
||||
diff -wB api.md api_orig.md
|
||||
# check version consistency
|
||||
CONFIG_VERSION=$(grep -Po '(?<=#define VERSION ")[^"]*' port/priv_include/config.h)
|
||||
CZ_VERSION=$(grep -Po '(?<=version: )[^"]*' .cz.yaml)
|
||||
COMP_VERSION=$(grep -Po '(?<=version: ")[^"]*' idf_component.yml)
|
||||
if [ "$CONFIG_VERSION" != "v$CZ_VERSION" ] || [ "$CONFIG_VERSION" != "v$COMP_VERSION" ]; then
|
||||
echo "Version mismatch detected:"
|
||||
echo "config.h: $CONFIG_VERSION"
|
||||
echo ".cz.yaml: $CZ_VERSION"
|
||||
echo "idf_component.yml: $COMP_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
echo "Versions are consistent: $CONFIG_VERSION"
|
||||
|
||||
build_idf_tests_with_mosq:
|
||||
if: |
|
||||
github.repository == 'espressif/esp-protocols' &&
|
||||
( contains(github.event.pull_request.labels.*.name, 'mosquitto') || github.event_name == 'push' )
|
||||
name: Build IDF tests
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
idf_target: ["esp32"]
|
||||
test: [ { app: publish, path: "tools/test_apps/protocols/mqtt/publish_connect_test" }]
|
||||
runs-on: ubuntu-20.04
|
||||
container: espressif/idf:${{ matrix.idf_ver }}
|
||||
env:
|
||||
TARGET_TEST_DIR: build_esp32_local_broker
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
|
||||
shell: bash
|
||||
run: |
|
||||
. ${IDF_PATH}/export.sh
|
||||
pip install idf-component-manager idf-build-apps --upgrade
|
||||
export MOSQUITTO_PATH=`pwd`/components/mosquitto
|
||||
# to use the actual version of mosquitto
|
||||
sed -i '/espressif\/mosquitto:/a \ \ \ \ override_path: "${MOSQUITTO_PATH}"' ${IDF_PATH}/${{matrix.test.path}}/main/idf_component.yml
|
||||
export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function"
|
||||
export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes"
|
||||
export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}"
|
||||
cd ${IDF_PATH}/${{matrix.test.path}}
|
||||
idf-build-apps find --config sdkconfig.ci.local_broker -vv --target ${{ matrix.idf_target }} --build-dir=${TARGET_TEST_DIR}
|
||||
idf-build-apps build --config sdkconfig.ci.local_broker -vv --target ${{ matrix.idf_target }} --build-dir=${TARGET_TEST_DIR}
|
||||
${GITHUB_WORKSPACE}/ci/clean_build_artifacts.sh `pwd`/${TARGET_TEST_DIR}
|
||||
# to replace mqtt test configs with specific mosquitto markers
|
||||
python ${MOSQUITTO_PATH}/test/replace_decorators.py pytest_mqtt_publish_app.py ${TARGET_TEST_DIR}/pytest_mosquitto.py
|
||||
zip -qur ${GITHUB_WORKSPACE}/artifacts.zip ${TARGET_TEST_DIR}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mosq_publish_esp32_${{ matrix.idf_ver }}
|
||||
path: artifacts.zip
|
||||
if-no-files-found: error
|
||||
|
||||
test_idf_ci_with_mosq:
|
||||
# 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, 'mosquitto') || github.event_name == 'push' )
|
||||
name: Mosquitto IDF target tests
|
||||
needs: build_idf_tests_with_mosq
|
||||
strategy:
|
||||
matrix:
|
||||
idf_ver: ["latest"]
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- ESP32-ETHERNET-KIT
|
||||
env:
|
||||
TEST_DIR: examples
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: mosq_publish_esp32_${{ matrix.idf_ver }}
|
||||
path: ${{ env.TEST_DIR }}/ci/
|
||||
- name: Run Test
|
||||
working-directory: ${{ env.TEST_DIR }}
|
||||
run: |
|
||||
python -m pip install pytest-embedded-serial-esp pytest-embedded-idf pytest-rerunfailures pytest-timeout pytest-ignore-test-results "paho-mqtt<2"
|
||||
unzip ci/artifacts.zip -d ci
|
||||
for dir in `ls -d ci/build_*`; do
|
||||
rm -rf build sdkconfig.defaults
|
||||
mv $dir build
|
||||
mv build/*.py .
|
||||
# Run only "test_mosquitto" marked tests
|
||||
python -m pytest --log-cli-level DEBUG --junit-xml=./results_esp32_${{ matrix.idf_ver }}_${dir#"ci/build_"}.xml --target=esp32 -m test_mosquitto
|
||||
done
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "components/asio/asio"]
|
||||
path = components/asio/asio
|
||||
url = https://github.com/espressif/asio
|
||||
url = https://github.com/chriskohlhoff/asio
|
||||
[submodule "components/mosquitto/mosquitto"]
|
||||
path = components/mosquitto/mosquitto
|
||||
url = https://github.com/eclipse/mosquitto
|
||||
|
@ -0,0 +1 @@
|
||||
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c
|
||||
|
@ -1,7 +1,8 @@
|
||||
---
|
||||
commitizen:
|
||||
bump_message: 'bump(asio): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py asio
|
||||
tag_format: asio-v$version
|
||||
version: 1.28.0~0
|
||||
version: 1.32.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## [1.32.0](https://github.com/espressif/esp-protocols/commits/asio-v1.32.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Upgrade asio to 1.32 ([9bdd429c](https://github.com/espressif/esp-protocols/commit/9bdd429c))
|
||||
- Drop esp/asio patches in favor of sock-utils ([27435b7f](https://github.com/espressif/esp-protocols/commit/27435b7f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix chat example to print only the message body ([76aaea08](https://github.com/espressif/esp-protocols/commit/76aaea08))
|
||||
- Make asio enable if_nametoindex to fix linking ([5db32cce](https://github.com/espressif/esp-protocols/commit/5db32cce))
|
||||
- Re-applie refs to common comps idf_component.yml ([9fe44a45](https://github.com/espressif/esp-protocols/commit/9fe44a45))
|
||||
- Reference common component from IDF ([74fc228c](https://github.com/espressif/esp-protocols/commit/74fc228c))
|
||||
- Revert referencing protocol_examples_common from IDF ([f9e0281a](https://github.com/espressif/esp-protocols/commit/f9e0281a))
|
||||
- reference protocol_examples_common from IDF ([09abb18b](https://github.com/espressif/esp-protocols/commit/09abb18b))
|
||||
- specify override_path in example manifest files ([1d8923cf](https://github.com/espressif/esp-protocols/commit/1d8923cf))
|
||||
|
||||
### Updated
|
||||
|
||||
- docs(asio): Updates asio docs ([ce9337d3](https://github.com/espressif/esp-protocols/commit/ce9337d3))
|
||||
|
||||
## [1.28.2~0](https://github.com/espressif/esp-protocols/commits/asio-1.28.2_0)
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -6,8 +6,8 @@ if(NOT CONFIG_LWIP_IPV6 AND NOT CMAKE_BUILD_EARLY_EXPANSION)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(asio_sources "asio/asio/src/asio.cpp")
|
||||
set(asio_requires lwip)
|
||||
set(asio_sources "asio/asio/src/asio.cpp" "port/src/asio_stub.cpp")
|
||||
set(asio_requires lwip sock_utils)
|
||||
|
||||
if(CONFIG_ASIO_SSL_SUPPORT)
|
||||
list(APPEND asio_sources
|
||||
@ -18,7 +18,7 @@ if(CONFIG_ASIO_SSL_SUPPORT)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${asio_sources}
|
||||
INCLUDE_DIRS "asio/asio/include" "port/include"
|
||||
INCLUDE_DIRS "port/include" "asio/asio/include"
|
||||
PRIV_INCLUDE_DIRS ${asio_priv_includes}
|
||||
PRIV_REQUIRES ${asio_requires})
|
||||
|
||||
@ -30,6 +30,7 @@ target_compile_definitions(${COMPONENT_LIB} PUBLIC SA_RESTART=0x01
|
||||
ASIO_STANDALONE
|
||||
ASIO_HAS_PTHREADS
|
||||
OPENSSL_NO_ENGINE
|
||||
ASIO_DETAIL_IMPL_POSIX_EVENT_IPP # this replaces asio's posix_event constructor
|
||||
)
|
||||
|
||||
if(NOT CONFIG_COMPILER_CXX_EXCEPTIONS)
|
||||
|
@ -1,6 +1,15 @@
|
||||
menu "ESP-ASIO"
|
||||
visible if LWIP_IPV6
|
||||
|
||||
config ASIO_IS_ENABLED
|
||||
# Invisible option that is enabled if ASIO is added to the IDF components.
|
||||
# This is used to "select" LWIP_NETIF_API option
|
||||
# which enables if_indextoname() and if_nametoindex() functions
|
||||
# (these are optionally used in asio)
|
||||
bool
|
||||
default "y"
|
||||
select LWIP_NETIF_API
|
||||
|
||||
config ASIO_SSL_SUPPORT
|
||||
bool "Enable SSL/TLS support of ASIO"
|
||||
default n
|
||||
|
Submodule components/asio/asio updated: a2e0f70d61...03ae834edb
@ -120,7 +120,7 @@ private:
|
||||
asio::buffer(read_msg_.body(), read_msg_.body_length()),
|
||||
[this, self](std::error_code ec, std::size_t /*length*/) {
|
||||
if (!ec) {
|
||||
ESP_LOGD("asio-chat:", "%s", read_msg_.body());
|
||||
ESP_LOGD("asio-chat", "%.*s", read_msg_.body_length(), read_msg_.body());
|
||||
room_.deliver(read_msg_);
|
||||
do_read_header();
|
||||
} else {
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "1.28.2~0"
|
||||
version: "1.32.0"
|
||||
description: Cross-platform C++ library for network and I/O programming
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/asio
|
||||
issues: https://github.com/espressif/esp-protocols/issues
|
||||
@ -7,3 +7,5 @@ repository: https://github.com/espressif/esp-protocols.git
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/sock_utils:
|
||||
version: "^0.1"
|
||||
|
11
components/asio/port/include/asio/detail/config.hpp
Normal file
11
components/asio/port/include/asio/detail/config.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "sys/socket.h"
|
||||
#include "socketpair.h"
|
||||
|
||||
#include_next "asio/detail/config.hpp"
|
@ -1,12 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef _ESP_ASIO_CONFIG_H_
|
||||
#define _ESP_ASIO_CONFIG_H_
|
||||
|
||||
#define ASIO_SSL_DETAIL_OPENSSL_TYPES_HPP
|
||||
#include "openssl_stub.hpp"
|
||||
|
||||
#endif // _ESP_ASIO_CONFIG_H_
|
@ -8,7 +8,7 @@
|
||||
//
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "openssl_stub.hpp"
|
||||
#include "asio/ssl/detail/openssl_types.hpp"
|
||||
#include <cstring>
|
||||
#include "asio/detail/throw_error.hpp"
|
||||
#include "asio/error.hpp"
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "openssl_stub.hpp"
|
||||
#include "asio/ssl/detail/openssl_types.hpp"
|
||||
#include "asio/detail/throw_error.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/ssl/detail/engine.hpp"
|
||||
|
36
components/asio/port/src/asio_stub.cpp
Normal file
36
components/asio/port/src/asio_stub.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2003-2023 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
// SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
#include "asio/detail/posix_event.hpp"
|
||||
#include "asio/detail/throw_error.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/detail/push_options.hpp"
|
||||
#include <unistd.h>
|
||||
#include <climits>
|
||||
|
||||
namespace asio::detail {
|
||||
// This replaces asio's posix_event constructor
|
||||
// since the default POSIX version uses pthread_condattr_t operations (init, setclock, destroy),
|
||||
// which are not available on all IDF versions (some are defined in compilers' headers, others in
|
||||
// pthread library, but they typically return `ENOSYS` which causes trouble in the event wrapper)
|
||||
// IMPORTANT: Check implementation of posix_event() when upgrading upstream asio in order not to
|
||||
// miss any initialization step.
|
||||
posix_event::posix_event()
|
||||
: state_(0)
|
||||
{
|
||||
int error = ::pthread_cond_init(&cond_, nullptr);
|
||||
asio::error_code ec(error, asio::error::get_system_category());
|
||||
asio::detail::throw_error(ec, "event");
|
||||
}
|
||||
} // namespace asio::detail
|
||||
|
||||
extern "C" int pause (void)
|
||||
{
|
||||
while (true) {
|
||||
::sleep(UINT_MAX);
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(modem): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py esp_modem
|
||||
tag_format: modem-v$version
|
||||
version: 1.2.1
|
||||
version: 1.3.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [1.3.0](https://github.com/espressif/esp-protocols/commits/modem-v1.3.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Add mode detection to the example ([18f196fa](https://github.com/espressif/esp-protocols/commit/18f196fa))
|
||||
- Support for pausing network in C-API ([1db83cd1](https://github.com/espressif/esp-protocols/commit/1db83cd1))
|
||||
- Add support for pausing netif ([247f1681](https://github.com/espressif/esp-protocols/commit/247f1681), [#699](https://github.com/espressif/esp-protocols/issues/699))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Minor cleanup of pppos example ([5e929902](https://github.com/espressif/esp-protocols/commit/5e929902))
|
||||
- Fix PPP mode detection to accept LCP/conf ([c989c6ad](https://github.com/espressif/esp-protocols/commit/c989c6ad))
|
||||
- Refine mode switch data->command ([8b6ea331](https://github.com/espressif/esp-protocols/commit/8b6ea331), [#692](https://github.com/espressif/esp-protocols/issues/692))
|
||||
- Detect serial ports properly ([0cb59ff8](https://github.com/espressif/esp-protocols/commit/0cb59ff8))
|
||||
- Fix CMUX enter to ignore URC before transition ([1284f66d](https://github.com/espressif/esp-protocols/commit/1284f66d), [#669](https://github.com/espressif/esp-protocols/issues/669))
|
||||
|
||||
## [1.2.1](https://github.com/espressif/esp-protocols/commits/modem-v1.2.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -76,4 +76,20 @@ menu "esp-modem"
|
||||
help
|
||||
If enabled, APIs to add URC handler are available
|
||||
|
||||
config ESP_MODEM_PPP_ESCAPE_BEFORE_EXIT
|
||||
bool "Send escape sequence when switching PPP -> CMD"
|
||||
default n
|
||||
help
|
||||
If enabled, the library sends a PPP escape ("+++" command)
|
||||
to switch to command mode. This make switching from PPP to CMD
|
||||
mode more robust for some devices (e.g. Quectel), but might cause
|
||||
trouble for other devices (e.g. SIMCOM).
|
||||
|
||||
config ESP_MODEM_ADD_DEBUG_LOGS
|
||||
bool "Add UART Tx/Rx logs"
|
||||
default n
|
||||
help
|
||||
If enabled, the library dumps all transmitted and received data.
|
||||
This option is only used for debugging.
|
||||
|
||||
endmenu
|
||||
|
@ -385,6 +385,17 @@ extern "C" void app_main(void)
|
||||
return 0;
|
||||
});
|
||||
#endif
|
||||
const ConsoleCommand PauseNetwork("pause_net", "toggle network pause", no_args, [&](ConsoleCommand * c) {
|
||||
static int cnt = 0;
|
||||
if (++cnt % 2) {
|
||||
ESP_LOGI(TAG, "Pausing netif");
|
||||
dce->pause_netif(true);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Unpausing netif");
|
||||
dce->pause_netif(false);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const struct SetApn {
|
||||
SetApn(): apn(STR1, nullptr, nullptr, "<apn>", "APN (Access Point Name)") {}
|
||||
|
@ -201,4 +201,23 @@ menu "Example Configuration"
|
||||
help
|
||||
MQTT data message, which we publish and expect to receive.
|
||||
|
||||
config EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
|
||||
bool "Demonstrate netif pause"
|
||||
default n
|
||||
help
|
||||
Set this to true to demonstrate network pausing.
|
||||
If enabled, the example waits for an MQTT data, then temporarily
|
||||
drops network to check signal quality, resumes networking and
|
||||
publishes another MQTT message.
|
||||
Connection to the MQTT broker should be kept.
|
||||
|
||||
config EXAMPLE_DETECT_MODE_BEFORE_CONNECT
|
||||
bool "Detect mode before connect"
|
||||
default n
|
||||
help
|
||||
Set this to true to demonstrate mode auto-detection.
|
||||
If enabled, the example tries to recognize the actual mode.
|
||||
If mode is detected correctly and it is not a command mode,
|
||||
then the example switches to command mode.
|
||||
|
||||
endmenu
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@ -34,6 +34,7 @@
|
||||
static const char *TAG = "pppos_example";
|
||||
static EventGroupHandle_t event_group = NULL;
|
||||
static const int CONNECT_BIT = BIT0;
|
||||
static const int DISCONNECT_BIT = BIT1;
|
||||
static const int GOT_DATA_BIT = BIT2;
|
||||
static const int USB_DISCONNECTED_BIT = BIT3; // Used only with USB DTE but we define it unconditionally, to avoid too many #ifdefs in the code
|
||||
|
||||
@ -55,6 +56,7 @@ static void usb_terminal_error_handler(esp_modem_terminal_error_t err)
|
||||
}
|
||||
#define CHECK_USB_DISCONNECTION(event_group) \
|
||||
if ((xEventGroupGetBits(event_group) & USB_DISCONNECTED_BIT) == USB_DISCONNECTED_BIT) { \
|
||||
ESP_LOGE(TAG, "USB_DISCONNECTED_BIT destroying modem dce"); \
|
||||
esp_modem_destroy(dce); \
|
||||
continue; \
|
||||
}
|
||||
@ -140,6 +142,7 @@ static void on_ip_event(void *arg, esp_event_base_t event_base,
|
||||
ESP_LOGI(TAG, "GOT ip event!!!");
|
||||
} else if (event_id == IP_EVENT_PPP_LOST_IP) {
|
||||
ESP_LOGI(TAG, "Modem Disconnect from PPP Server");
|
||||
xEventGroupSetBits(event_group, DISCONNECT_BIT);
|
||||
} else if (event_id == IP_EVENT_GOT_IP6) {
|
||||
ESP_LOGI(TAG, "GOT IPv6 event!");
|
||||
|
||||
@ -158,6 +161,7 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, NULL));
|
||||
|
||||
/* Configure the PPP netif */
|
||||
esp_err_t err;
|
||||
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_PPP_APN);
|
||||
esp_netif_config_t netif_ppp_config = ESP_NETIF_DEFAULT_PPP();
|
||||
esp_netif_t *esp_netif = esp_netif_new(&netif_ppp_config);
|
||||
@ -205,7 +209,7 @@ void app_main(void)
|
||||
#endif
|
||||
assert(dce);
|
||||
if (dte_config.uart_config.flow_control == ESP_MODEM_FLOW_CONTROL_HW) {
|
||||
esp_err_t err = esp_modem_set_flow_control(dce, 2, 2); //2/2 means HW Flow Control.
|
||||
err = esp_modem_set_flow_control(dce, 2, 2); //2/2 means HW Flow Control.
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set the set_flow_control mode");
|
||||
return;
|
||||
@ -246,7 +250,27 @@ void app_main(void)
|
||||
#error Invalid serial connection to modem.
|
||||
#endif
|
||||
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT);
|
||||
#if CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
|
||||
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_DETECT);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_DETECT) failed with %d", err);
|
||||
return;
|
||||
}
|
||||
esp_modem_dce_mode_t mode = esp_modem_get_mode(dce);
|
||||
ESP_LOGI(TAG, "Mode detection completed: current mode is: %d", mode);
|
||||
if (mode == ESP_MODEM_MODE_DATA) { // set back to command mode
|
||||
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Command mode restored");
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT
|
||||
|
||||
xEventGroupClearBits(event_group, CONNECT_BIT | GOT_DATA_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT);
|
||||
|
||||
/* Run the modem demo app */
|
||||
#if CONFIG_EXAMPLE_NEED_SIM_PIN == 1
|
||||
@ -262,7 +286,7 @@ void app_main(void)
|
||||
#endif
|
||||
|
||||
int rssi, ber;
|
||||
esp_err_t err = esp_modem_get_signal_quality(dce, &rssi, &ber);
|
||||
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_get_signal_quality failed with %d %s", err, esp_err_to_name(err));
|
||||
return;
|
||||
@ -301,22 +325,41 @@ void app_main(void)
|
||||
}
|
||||
/* Wait for IP address */
|
||||
ESP_LOGI(TAG, "Waiting for IP address");
|
||||
xEventGroupWaitBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
xEventGroupWaitBits(event_group, CONNECT_BIT | USB_DISCONNECTED_BIT | DISCONNECT_BIT, pdFALSE, pdFALSE,
|
||||
pdMS_TO_TICKS(60000));
|
||||
CHECK_USB_DISCONNECTION(event_group);
|
||||
if ((xEventGroupGetBits(event_group) & CONNECT_BIT) != CONNECT_BIT) {
|
||||
ESP_LOGW(TAG, "Modem not connected, switching back to the command mode");
|
||||
err = esp_modem_set_mode(dce, ESP_MODEM_MODE_COMMAND);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_set_mode(ESP_MODEM_MODE_COMMAND) failed with %d", err);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Command mode restored");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Config MQTT */
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
||||
esp_mqtt_client_config_t mqtt_config = {
|
||||
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
|
||||
};
|
||||
#else
|
||||
esp_mqtt_client_config_t mqtt_config = {
|
||||
.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
|
||||
};
|
||||
#endif
|
||||
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
|
||||
esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
||||
esp_mqtt_client_start(mqtt_client);
|
||||
|
||||
#if CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
|
||||
xEventGroupWaitBits(event_group, GOT_DATA_BIT, pdTRUE, pdFALSE, portMAX_DELAY);
|
||||
esp_modem_pause_net(dce, true);
|
||||
err = esp_modem_get_signal_quality(dce, &rssi, &ber);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_modem_get_signal_quality failed with %d", err);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Signal quality: rssi=%d, ber=%d", rssi, ber);
|
||||
esp_modem_pause_net(dce, false);
|
||||
esp_mqtt_client_publish(mqtt_client, CONFIG_EXAMPLE_MQTT_TEST_TOPIC, CONFIG_EXAMPLE_MQTT_TEST_DATA, 0, 0, 0);
|
||||
#endif // CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for MQTT data");
|
||||
xEventGroupWaitBits(event_group, GOT_DATA_BIT | USB_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
|
||||
CHECK_USB_DISCONNECTION(event_group);
|
||||
|
@ -12,7 +12,7 @@ def test_pppos_connect(dut):
|
||||
4. checks that the client cleanly disconnects
|
||||
"""
|
||||
# Check the sequence of connecting, publishing, disconnecting
|
||||
dut.expect('Modem Connect to PPP Server')
|
||||
dut.expect('Modem Connect to PPP Server', timeout=90)
|
||||
# Check for MQTT connection and the data event
|
||||
dut.expect('MQTT_EVENT_CONNECTED')
|
||||
dut.expect('MQTT_EVENT_DATA')
|
||||
|
@ -11,5 +11,7 @@ CONFIG_EXAMPLE_MODEM_DEVICE_SIM800=y
|
||||
CONFIG_EXAMPLE_MODEM_DEVICE_BG96=n
|
||||
CONFIG_EXAMPLE_MODEM_PPP_APN="lpwa.vodafone.com"
|
||||
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
|
||||
CONFIG_EXAMPLE_PAUSE_NETIF_TO_CHECK_SIGNAL=y
|
||||
CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y
|
||||
CONFIG_ESP32_PANIC_PRINT_HALT=y
|
||||
CONFIG_EXAMPLE_DETECT_MODE_BEFORE_CONNECT=y
|
||||
|
@ -16,3 +16,4 @@ CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_EXAMPLE_CLOSE_CMUX_AT_END=y
|
||||
CONFIG_EXAMPLE_MQTT_TEST_TOPIC="/ci/esp-modem/pppos-client"
|
||||
CONFIG_BROKER_URI="mqtt://mqtt.eclipseprojects.io"
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
description: Library for communicating with cellular modems in command and data modes
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
|
||||
issues: https://github.com/espressif/esp-protocols/issues
|
||||
|
@ -91,6 +91,11 @@ public:
|
||||
return mode.set(dte.get(), device.get(), netif, m);
|
||||
}
|
||||
|
||||
modem_mode get_mode()
|
||||
{
|
||||
return mode.get();
|
||||
}
|
||||
|
||||
bool recover()
|
||||
{
|
||||
return dte->recover();
|
||||
@ -103,6 +108,29 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Pauses/Unpauses network temporarily
|
||||
* @param do_pause true to pause, false to unpause
|
||||
* @param force true to ignore command failures and continue
|
||||
* @return command_result of the underlying commands
|
||||
*/
|
||||
command_result pause_netif(bool do_pause, bool force = false, int delay = 1000)
|
||||
{
|
||||
command_result result;
|
||||
if (do_pause) {
|
||||
netif.pause();
|
||||
Task::Delay(delay); // Mandatory 1s pause before
|
||||
dte->set_command_callbacks();
|
||||
result = device->set_command_mode();
|
||||
} else {
|
||||
result = device->resume_data_mode();
|
||||
if (result == command_result::OK || force) {
|
||||
netif.resume();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<DTE> dte;
|
||||
std::shared_ptr<SpecificModule> device;
|
||||
|
@ -77,7 +77,9 @@ public:
|
||||
if (set_command_mode() == command_result::OK) {
|
||||
return true;
|
||||
}
|
||||
Task::Delay(1000); // Mandatory 1s pause after escape
|
||||
// send a newline to delimit the escape from the upcoming sync command
|
||||
uint8_t delim = '\n';
|
||||
dte->write(&delim, 1);
|
||||
if (sync() == command_result::OK) {
|
||||
return true;
|
||||
}
|
||||
|
@ -145,6 +145,12 @@ public:
|
||||
*/
|
||||
bool recover();
|
||||
|
||||
/**
|
||||
* @brief Set internal command callbacks to the underlying terminal.
|
||||
* Here we capture command replies to be processed by supplied command callbacks in struct command_cb.
|
||||
*/
|
||||
void set_command_callbacks();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Allows for locking the DTE
|
||||
@ -204,12 +210,6 @@ private:
|
||||
} inflatable;
|
||||
#endif // CONFIG_ESP_MODEM_USE_INFLATABLE_BUFFER_IF_NEEDED
|
||||
|
||||
/**
|
||||
* @brief Set internal command callbacks to the underlying terminal.
|
||||
* Here we capture command replies to be processed by supplied command callbacks in struct command_cb.
|
||||
*/
|
||||
void set_command_callbacks();
|
||||
|
||||
/**
|
||||
* @brief This abstracts command callback processing and implements its locking, signaling of completion and timeouts.
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -54,6 +54,16 @@ public:
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* @brief Pause the network interface
|
||||
*/
|
||||
void pause();
|
||||
|
||||
/**
|
||||
* @brief Resume the network interface
|
||||
*/
|
||||
void resume();
|
||||
|
||||
void receive(uint8_t *data, size_t len);
|
||||
|
||||
private:
|
||||
|
@ -45,6 +45,8 @@ typedef enum esp_modem_dce_mode {
|
||||
ESP_MODEM_MODE_CMUX_MANUAL_SWAP, /**< Swap terminals in CMUX manual mode */
|
||||
ESP_MODEM_MODE_CMUX_MANUAL_DATA, /**< Set DATA mode in CMUX manual mode */
|
||||
ESP_MODEM_MODE_CMUX_MANUAL_COMMAND, /**< Set COMMAND mode in CMUX manual mode */
|
||||
ESP_MODEM_MODE_DETECT, /**< Detect the mode and resume it (if sucessfully detected) */
|
||||
ESP_MODEM_MODE_UNDEF,
|
||||
} esp_modem_dce_mode_t;
|
||||
|
||||
/**
|
||||
@ -160,6 +162,18 @@ esp_err_t esp_modem_set_apn(esp_modem_dce_t *dce, const char *apn);
|
||||
esp_err_t esp_modem_set_urc(esp_modem_dce_t *dce, esp_err_t(*got_line_cb)(uint8_t *data, size_t len));
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief This API provides support for temporarily pausing networking in order
|
||||
* to send/receive AT commands and resume networking afterwards.
|
||||
* @note This function does not switch modes, the modem is still in data mode.
|
||||
*
|
||||
* @param dce Modem DCE handle
|
||||
* @param pause true to pause the network interface, false to resume networking
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t esp_modem_pause_net(esp_modem_dce_t *dce, bool pause);
|
||||
|
||||
esp_modem_dce_mode_t esp_modem_get_mode(esp_modem_dce_t *dce);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
@ -95,12 +95,43 @@ extern "C" esp_err_t esp_modem_sync(esp_modem_dce_t *dce_wrap)
|
||||
return command_response_to_esp_err(dce_wrap->dce->sync());
|
||||
}
|
||||
|
||||
extern "C" esp_modem_dce_mode_t esp_modem_get_mode(esp_modem_dce_t *dce_wrap)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_MODEM_MODE_UNDEF;
|
||||
}
|
||||
auto mode = dce_wrap->dce->get_mode();
|
||||
switch (mode) {
|
||||
default:
|
||||
case modem_mode::UNDEF:
|
||||
return ESP_MODEM_MODE_UNDEF;
|
||||
case modem_mode::COMMAND_MODE:
|
||||
return ESP_MODEM_MODE_COMMAND;
|
||||
case modem_mode::DATA_MODE:
|
||||
return ESP_MODEM_MODE_DATA;
|
||||
case modem_mode::CMUX_MODE:
|
||||
return ESP_MODEM_MODE_CMUX;
|
||||
case modem_mode::CMUX_MANUAL_MODE:
|
||||
return ESP_MODEM_MODE_CMUX_MANUAL;
|
||||
case modem_mode::CMUX_MANUAL_EXIT:
|
||||
return ESP_MODEM_MODE_CMUX_MANUAL_EXIT;
|
||||
case modem_mode::CMUX_MANUAL_DATA:
|
||||
return ESP_MODEM_MODE_CMUX_MANUAL_DATA;
|
||||
case modem_mode::CMUX_MANUAL_COMMAND:
|
||||
return ESP_MODEM_MODE_CMUX_MANUAL_COMMAND;
|
||||
case modem_mode::CMUX_MANUAL_SWAP:
|
||||
return ESP_MODEM_MODE_CMUX_MANUAL_SWAP;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce_mode_t mode)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
switch (mode) {
|
||||
case ESP_MODEM_MODE_UNDEF:
|
||||
return dce_wrap->dce->set_mode(modem_mode::UNDEF) ? ESP_OK : ESP_FAIL;
|
||||
case ESP_MODEM_MODE_DATA:
|
||||
return dce_wrap->dce->set_mode(modem_mode::DATA_MODE) ? ESP_OK : ESP_FAIL;
|
||||
case ESP_MODEM_MODE_COMMAND:
|
||||
@ -117,6 +148,8 @@ extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce
|
||||
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_DATA) ? ESP_OK : ESP_FAIL;
|
||||
case ESP_MODEM_MODE_CMUX_MANUAL_COMMAND:
|
||||
return dce_wrap->dce->set_mode(modem_mode::CMUX_MANUAL_COMMAND) ? ESP_OK : ESP_FAIL;
|
||||
case ESP_MODEM_MODE_DETECT:
|
||||
return dce_wrap->dce->set_mode(modem_mode::AUTODETECT) ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
@ -475,3 +508,19 @@ extern "C" esp_err_t esp_modem_set_urc(esp_modem_dce_t *dce_wrap, esp_err_t(*got
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" esp_err_t esp_modem_pause_net(esp_modem_dce_t *dce_wrap, bool pause)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return command_response_to_esp_err(dce_wrap->dce->pause_netif(pause));
|
||||
}
|
||||
|
||||
extern "C" esp_err_t esp_modem_hang_up(esp_modem_dce_t *dce_wrap)
|
||||
{
|
||||
if (dce_wrap == nullptr || dce_wrap->dce == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return command_response_to_esp_err(dce_wrap->dce->hang_up());
|
||||
}
|
||||
|
@ -123,7 +123,12 @@ bool CMux::data_available(uint8_t *data, size_t len)
|
||||
{
|
||||
if (data && (type & FT_UIH) == FT_UIH && len > 0 && dlci > 0) { // valid payload on a virtual term
|
||||
int virtual_term = dlci - 1;
|
||||
if (virtual_term < MAX_TERMINALS_NUM && read_cb[virtual_term]) {
|
||||
if (virtual_term < MAX_TERMINALS_NUM) {
|
||||
if (read_cb[virtual_term] == nullptr) {
|
||||
// ignore all virtual terminal's data before we completely establish CMUX
|
||||
ESP_LOG_BUFFER_HEXDUMP("CMUX Rx before init", data, len, ESP_LOG_DEBUG);
|
||||
return true;
|
||||
}
|
||||
// Post partial data (or defragment to post on CMUX footer)
|
||||
#ifdef DEFRAGMENT_CMUX_PAYLOAD
|
||||
if (payload_start == nullptr) {
|
||||
@ -142,7 +147,11 @@ bool CMux::data_available(uint8_t *data, size_t len)
|
||||
sabm_ack = dlci;
|
||||
} else if (data == nullptr && dlci > 0) {
|
||||
int virtual_term = dlci - 1;
|
||||
if (virtual_term < MAX_TERMINALS_NUM && read_cb[virtual_term]) {
|
||||
if (virtual_term < MAX_TERMINALS_NUM) {
|
||||
if (read_cb[virtual_term] == nullptr) {
|
||||
// silently ignore this CMUX frame (not finished entering CMUX, yet)
|
||||
return true;
|
||||
}
|
||||
#ifdef DEFRAGMENT_CMUX_PAYLOAD
|
||||
read_cb[virtual_term](payload_start, total_payload_size);
|
||||
#endif
|
||||
|
@ -18,7 +18,6 @@ namespace transitions {
|
||||
|
||||
static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
|
||||
{
|
||||
netif.stop();
|
||||
auto signal = std::make_shared<SignalGroup>();
|
||||
std::weak_ptr<SignalGroup> weak_signal = signal;
|
||||
dte.set_read_cb([&netif, weak_signal](uint8_t *data, size_t len) -> bool {
|
||||
@ -32,7 +31,7 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
|
||||
if (memchr(data, '\n', len))
|
||||
{
|
||||
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data (CMD)", data, len, ESP_LOG_DEBUG);
|
||||
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
|
||||
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED", "OK"});
|
||||
std::string_view response((char *) data, len);
|
||||
for (auto &it : pass)
|
||||
if (response.find(it) != std::string::npos) {
|
||||
@ -44,8 +43,14 @@ static bool exit_data(DTE &dte, ModuleIf &device, Netif &netif)
|
||||
}
|
||||
return false;
|
||||
});
|
||||
netif.stop();
|
||||
netif.wait_until_ppp_exits();
|
||||
if (!signal->wait(1, 2000)) {
|
||||
#ifdef ESP_MODEM_PPP_ESCAPE_BEFORE_EXIT
|
||||
std::array<uint8_t, 3> ppp_escape = {'+', '+', '+'};
|
||||
dte.write(ppp_escape.data(), ppp_escape.size());
|
||||
#endif
|
||||
if (!signal->wait(1, 2000)) { // wait for any of the disconnection messages
|
||||
// if no reply -> set device to command mode
|
||||
dte.set_read_cb(nullptr);
|
||||
if (!device.set_mode(modem_mode::COMMAND_MODE)) {
|
||||
return false;
|
||||
@ -323,12 +328,19 @@ modem_mode DCE_Mode::guess_unsafe(DTE *dte, bool with_cmux)
|
||||
if (reply_pos >= sizeof(probe::ppp::lcp_echo_reply_head)) {
|
||||
// check for initial 2 bytes
|
||||
auto *ptr = static_cast<uint8_t *>(memmem(reply, reply_pos, probe::ppp::lcp_echo_reply_head.data(), 2));
|
||||
// and check the other two bytes for protocol ID: LCP
|
||||
// and check the other two bytes for protocol ID:
|
||||
// * either LCP reply
|
||||
if (ptr && ptr[3] == probe::ppp::lcp_echo_reply_head[3] && ptr[4] == probe::ppp::lcp_echo_reply_head[4]) {
|
||||
if (auto signal = weak_signal.lock()) {
|
||||
signal->set(probe::ppp::mode);
|
||||
}
|
||||
}
|
||||
// * or LCP conf request
|
||||
if (ptr && ptr[3] == probe::ppp::lcp_echo_request[3] && ptr[4] == probe::ppp::lcp_echo_request[4]) {
|
||||
if (auto signal = weak_signal.lock()) {
|
||||
signal->set(probe::ppp::mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reply_pos >= 4 && memmem(reply, reply_pos, probe::cmd::reply, sizeof(probe::cmd::reply))) {
|
||||
if (reply[0] != 0xf9) { // double check that the reply is not wrapped in CMUX headers
|
||||
|
@ -99,6 +99,20 @@ void Netif::stop()
|
||||
signal.clear(PPP_STARTED);
|
||||
}
|
||||
|
||||
void Netif::resume()
|
||||
{
|
||||
ppp_dte->set_read_cb([this](uint8_t *data, size_t len) -> bool {
|
||||
receive(data, len);
|
||||
return true;
|
||||
});
|
||||
signal.set(PPP_STARTED);
|
||||
}
|
||||
|
||||
void Netif::pause()
|
||||
{
|
||||
signal.clear(PPP_STARTED);
|
||||
}
|
||||
|
||||
Netif::~Netif()
|
||||
{
|
||||
if (signal.is_any(PPP_STARTED)) {
|
||||
|
@ -52,7 +52,6 @@ void Netif::start()
|
||||
|
||||
void Netif::stop()
|
||||
{
|
||||
ppp_dte->set_read_cb(nullptr);
|
||||
signal.clear(PPP_STARTED);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -176,13 +176,20 @@ int UartTerminal::read(uint8_t *data, size_t len)
|
||||
uart_get_buffered_data_len(uart.port, &length);
|
||||
length = std::min(len, length);
|
||||
if (length > 0) {
|
||||
return uart_read_bytes(uart.port, data, length, portMAX_DELAY);
|
||||
int read_len = uart_read_bytes(uart.port, data, length, portMAX_DELAY);
|
||||
#if CONFIG_ESP_MODEM_ADD_DEBUG_LOGS
|
||||
ESP_LOG_BUFFER_HEXDUMP("uart-rx", data, read_len, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
return read_len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UartTerminal::write(uint8_t *data, size_t len)
|
||||
{
|
||||
#if CONFIG_ESP_MODEM_ADD_DEBUG_LOGS
|
||||
ESP_LOG_BUFFER_HEXDUMP("uart-tx", data, len, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
return uart_write_bytes_compat(uart.port, data, len);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,20 @@ from threading import Event, Thread
|
||||
import netifaces
|
||||
|
||||
|
||||
def is_esp32(port):
|
||||
"""
|
||||
Check if the given port is connected to an ESP32 using esptool.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['esptool.py', '--port', port, 'chip_id'],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, text=True
|
||||
)
|
||||
return 'ESP32' in result.stdout
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def run_server(server_stop, port, server_ip, client_ip, auth, auth_user, auth_password):
|
||||
print('Starting PPP server on port: {}'.format(port))
|
||||
try:
|
||||
@ -66,13 +80,27 @@ def test_examples_protocol_pppos_connect(dut):
|
||||
)
|
||||
raise
|
||||
|
||||
# the PPP test env uses two ttyUSB's: one for ESP32 board, another one for ppp server
|
||||
# use the other port for PPP server than the DUT/ESP
|
||||
port = '/dev/ttyUSB0' if dut.serial.port == '/dev/ttyUSB1' else '/dev/ttyUSB1'
|
||||
# the PPP test env uses three ttyUSB's: two for ESP32 board and another one for the ppp server
|
||||
# we need to detect the server_port (for PPPD)
|
||||
server_port = None
|
||||
for i in ['/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2']:
|
||||
if i == dut.serial.port:
|
||||
print(f'DUT port: {i}')
|
||||
elif is_esp32(i):
|
||||
print(f'Some other ESP32: {i}')
|
||||
else:
|
||||
print(f'Port for PPPD: {i}')
|
||||
server_port = i
|
||||
if server_port is None:
|
||||
print(
|
||||
'ENV_TEST_FAILURE: Cannot locate PPPD port'
|
||||
)
|
||||
raise
|
||||
|
||||
# Start the PPP server
|
||||
server_stop = Event()
|
||||
t = Thread(target=run_server,
|
||||
args=(server_stop, port, server_ip, client_ip, auth, auth_user, auth_password))
|
||||
args=(server_stop, server_port, server_ip, client_ip, auth, auth_user, auth_password))
|
||||
t.start()
|
||||
try:
|
||||
ppp_server_timeout = time.time() + 30
|
||||
|
@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(websocket): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py esp_websocket_client
|
||||
tag_format: websocket-v$version
|
||||
version: 1.3.0
|
||||
version: 1.4.0
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## [1.4.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.4.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Support DS peripheral for mutual TLS ([55385ec3](https://github.com/espressif/esp-protocols/commit/55385ec3))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- wait for task on destroy ([42674b49](https://github.com/espressif/esp-protocols/commit/42674b49))
|
||||
- Fix pytest to verify client correctly ([9046af8f](https://github.com/espressif/esp-protocols/commit/9046af8f))
|
||||
- propagate error type ([eeeb9006](https://github.com/espressif/esp-protocols/commit/eeeb9006))
|
||||
- fix example buffer leak ([5219c39d](https://github.com/espressif/esp-protocols/commit/5219c39d))
|
||||
|
||||
### Updated
|
||||
|
||||
- chore(websocket): align structure members ([beb6e57e](https://github.com/espressif/esp-protocols/commit/beb6e57e))
|
||||
- chore(websocket): remove unused client variable ([15d3a01e](https://github.com/espressif/esp-protocols/commit/15d3a01e))
|
||||
|
||||
## [1.3.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.3.0)
|
||||
|
||||
### Features
|
||||
|
@ -93,6 +93,9 @@ typedef struct {
|
||||
size_t client_cert_len;
|
||||
const char *client_key;
|
||||
size_t client_key_len;
|
||||
#if CONFIG_ESP_TLS_USE_DS_PERIPHERAL
|
||||
void *client_ds_data;
|
||||
#endif
|
||||
bool use_global_ca_store;
|
||||
bool skip_cert_common_name_check;
|
||||
const char *cert_common_name;
|
||||
@ -122,12 +125,11 @@ struct esp_websocket_client {
|
||||
uint64_t ping_tick_ms;
|
||||
uint64_t pingpong_tick_ms;
|
||||
int wait_timeout_ms;
|
||||
int auto_reconnect;
|
||||
bool run;
|
||||
bool wait_for_pong_resp;
|
||||
bool selected_for_destroying;
|
||||
EventGroupHandle_t status_bits;
|
||||
SemaphoreHandle_t lock;
|
||||
SemaphoreHandle_t lock;
|
||||
size_t errormsg_size;
|
||||
char *errormsg_buffer;
|
||||
char *rx_buffer;
|
||||
@ -235,9 +237,9 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand
|
||||
} else {
|
||||
client->reconnect_tick_ms = _tick_get_ms();
|
||||
ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
|
||||
client->error_handle.error_type = error_type;
|
||||
client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
|
||||
}
|
||||
client->error_handle.error_type = error_type;
|
||||
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -447,6 +449,21 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
|
||||
client = NULL;
|
||||
}
|
||||
|
||||
static esp_err_t stop_wait_task(esp_websocket_client_handle_t client)
|
||||
{
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme)
|
||||
{
|
||||
esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme);
|
||||
@ -532,6 +549,10 @@ static esp_err_t esp_websocket_client_create_transport(esp_websocket_client_hand
|
||||
} else {
|
||||
esp_transport_ssl_set_client_key_data_der(ssl, client->config->client_key, client->config->client_key_len);
|
||||
}
|
||||
#if CONFIG_ESP_TLS_USE_DS_PERIPHERAL
|
||||
} else if (client->config->client_ds_data) {
|
||||
esp_transport_ssl_set_ds_data(ssl, client->config->client_ds_data);
|
||||
#endif
|
||||
}
|
||||
if (client->config->crt_bundle_attach) {
|
||||
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
|
||||
@ -697,6 +718,9 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
|
||||
client->config->client_cert_len = config->client_cert_len;
|
||||
client->config->client_key = config->client_key;
|
||||
client->config->client_key_len = config->client_key_len;
|
||||
#if CONFIG_ESP_TLS_USE_DS_PERIPHERAL
|
||||
client->config->client_ds_data = config->client_ds_data;
|
||||
#endif
|
||||
client->config->skip_cert_common_name_check = config->skip_cert_common_name_check;
|
||||
client->config->cert_common_name = config->cert_common_name;
|
||||
client->config->crt_bundle_attach = config->crt_bundle_attach;
|
||||
@ -745,6 +769,7 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
|
||||
ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, {
|
||||
goto _websocket_init_fail;
|
||||
});
|
||||
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
|
||||
|
||||
client->buffer_size = buffer_size;
|
||||
return client;
|
||||
@ -759,9 +784,11 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (client->run) {
|
||||
esp_websocket_client_stop(client);
|
||||
|
||||
if (client->status_bits && (STOPPED_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
|
||||
stop_wait_task(client);
|
||||
}
|
||||
|
||||
destroy_and_free_resources(client);
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -1150,23 +1177,13 @@ esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client)
|
||||
if (client == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!client->run) {
|
||||
|
||||
if (xEventGroupGetBits(client->status_bits) & STOPPED_BIT) {
|
||||
ESP_LOGW(TAG, "Client was not started");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* A running client cannot be stopped from the websocket task/event handler */
|
||||
TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
|
||||
if (running_task == client->task_handle) {
|
||||
ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
|
||||
client->run = false;
|
||||
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
|
||||
client->state = WEBSOCKET_STATE_UNKNOW;
|
||||
return ESP_OK;
|
||||
return stop_wait_task(client);
|
||||
}
|
||||
|
||||
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout)
|
||||
|
@ -55,7 +55,7 @@ class Websocket(object):
|
||||
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.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
|
||||
else:
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "1.3.0"
|
||||
version: "1.4.0"
|
||||
description: WebSocket protocol client for ESP-IDF
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client
|
||||
dependencies:
|
||||
|
@ -108,10 +108,13 @@ typedef struct {
|
||||
int buffer_size; /*!< Websocket buffer size */
|
||||
const char *cert_pem; /*!< Pointer to certificate data in PEM or DER format for server verify (with SSL), default is NULL, not required to verify the server. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in cert_len. */
|
||||
size_t cert_len; /*!< Length of the buffer pointed to by cert_pem. May be 0 for null-terminated pem */
|
||||
const char *client_cert; /*!< Pointer to certificate data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_cert_len. */
|
||||
const char *client_cert; /*!< Pointer to certificate data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_key` or `client_ds_data` (if supported) has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_cert_len. */
|
||||
size_t client_cert_len; /*!< Length of the buffer pointed to by client_cert. May be 0 for null-terminated pem */
|
||||
const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */
|
||||
const char *client_key; /*!< Pointer to private key data in PEM or DER format for SSL mutual authentication, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided and `client_ds_data` (if supported) gets ignored. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in client_key_len */
|
||||
size_t client_key_len; /*!< Length of the buffer pointed to by client_key_pem. May be 0 for null-terminated pem */
|
||||
#if CONFIG_ESP_TLS_USE_DS_PERIPHERAL
|
||||
void *client_ds_data; /*!< Pointer to the encrypted private key data for SSL mutual authentication using the DS peripheral, default is NULL, not required if mutual authentication is not needed. If it is not NULL, also `client_cert` has to be provided. It is ignored if `client_key` is provided */
|
||||
#endif
|
||||
esp_websocket_transport_t transport; /*!< Websocket transport type, see `esp_websocket_transport_t */
|
||||
const char *subprotocol; /*!< Websocket subprotocol */
|
||||
const char *user_agent; /*!< Websocket user-agent */
|
||||
|
@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(mdns): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py mdns
|
||||
tag_format: mdns-v$version
|
||||
version: 1.4.2
|
||||
version: 1.5.3
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,51 @@
|
||||
# Changelog
|
||||
|
||||
## [1.5.3](https://github.com/espressif/esp-protocols/commits/mdns-v1.5.3)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix responder to ignore only invalid queries ([cd07228f](https://github.com/espressif/esp-protocols/commit/cd07228f), [#754](https://github.com/espressif/esp-protocols/issues/754))
|
||||
|
||||
## [1.5.2](https://github.com/espressif/esp-protocols/commits/mdns-v1.5.2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix potential NULL deref when sending sub-buy ([e7273c46](https://github.com/espressif/esp-protocols/commit/e7273c46))
|
||||
- Fix _mdns_append_fqdn excessive stack usage ([bd23c233](https://github.com/espressif/esp-protocols/commit/bd23c233))
|
||||
|
||||
## [1.5.1](https://github.com/espressif/esp-protocols/commits/mdns-v1.5.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix incorrect memory free for mdns browse ([4451a8c5](https://github.com/espressif/esp-protocols/commit/4451a8c5))
|
||||
|
||||
## [1.5.0](https://github.com/espressif/esp-protocols/commits/mdns-v1.5.0)
|
||||
|
||||
### Features
|
||||
|
||||
- supported removal of subtype when updating service ([4ad88e29](https://github.com/espressif/esp-protocols/commit/4ad88e29))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix zero-sized VLA clang-tidy warnings ([196198ec](https://github.com/espressif/esp-protocols/commit/196198ec))
|
||||
- Remove dead store to arg variable shared ([e838bf03](https://github.com/espressif/esp-protocols/commit/e838bf03))
|
||||
- Fix name mangling not to use strcpy() ([99b54ac3](https://github.com/espressif/esp-protocols/commit/99b54ac3))
|
||||
- Fix potential null derefernce in _mdns_execute_action() ([f5be2f41](https://github.com/espressif/esp-protocols/commit/f5be2f41))
|
||||
- Fix AFL test mock per espressif/esp-idf@a5bc08fb55c ([3d8835cf](https://github.com/espressif/esp-protocols/commit/3d8835cf))
|
||||
- Fixed potential out-of-bound interface error ([24f55ce9](https://github.com/espressif/esp-protocols/commit/24f55ce9))
|
||||
- Fixed incorrect error conversion ([8f8516cc](https://github.com/espressif/esp-protocols/commit/8f8516cc))
|
||||
- Fixed potential overflow when allocating txt data ([75a8e864](https://github.com/espressif/esp-protocols/commit/75a8e864))
|
||||
- Move MDNS_NAME_BUF_LEN to public headers ([907087c0](https://github.com/espressif/esp-protocols/commit/907087c0), [#724](https://github.com/espressif/esp-protocols/issues/724))
|
||||
- Cleanup includes in mdns.c ([68a9e148](https://github.com/espressif/esp-protocols/commit/68a9e148), [#725](https://github.com/espressif/esp-protocols/issues/725))
|
||||
- Allow advertizing service with port==0 ([827ea65f](https://github.com/espressif/esp-protocols/commit/827ea65f))
|
||||
- Fixed complier warning if MDNS_MAX_SERVICES==0 ([95377216](https://github.com/espressif/esp-protocols/commit/95377216), [#611](https://github.com/espressif/esp-protocols/issues/611))
|
||||
|
||||
## [1.4.3](https://github.com/espressif/esp-protocols/commits/mdns-v1.4.3)
|
||||
|
||||
### Features
|
||||
|
||||
- support zero item when update subtype ([5bd82c01](https://github.com/espressif/esp-protocols/commit/5bd82c01))
|
||||
|
||||
## [1.4.2](https://github.com/espressif/esp-protocols/commits/mdns-v1.4.2)
|
||||
|
||||
### Features
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "1.4.2"
|
||||
version: "1.5.3"
|
||||
description: "Multicast UDP service used to provide local network service and host discovery."
|
||||
url: "https://github.com/espressif/esp-protocols/tree/master/components/mdns"
|
||||
issues: "https://github.com/espressif/esp-protocols/issues"
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -10,6 +10,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include <esp_netif.h>
|
||||
|
||||
#define MDNS_TYPE_A 0x0001
|
||||
@ -21,6 +22,13 @@ extern "C" {
|
||||
#define MDNS_TYPE_NSEC 0x002F
|
||||
#define MDNS_TYPE_ANY 0x00FF
|
||||
|
||||
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
|
||||
#define MDNS_NAME_MAX_LEN (64+4) // Need to account for IPv6 reverse queries (64 char address + ".ip6" )
|
||||
#else
|
||||
#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto
|
||||
#endif
|
||||
#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto
|
||||
|
||||
/**
|
||||
* @brief Asynchronous query handle
|
||||
*/
|
||||
@ -615,6 +623,8 @@ esp_err_t mdns_service_subtype_add_multiple_items_for_host(const char *instance_
|
||||
* @param subtype the pointer of subtype array to add.
|
||||
* @param num_items number of items in subtype array
|
||||
*
|
||||
* @note If `num_items` is 0, then remove all subtypes.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK success
|
||||
* - ESP_ERR_INVALID_ARG Parameter error
|
||||
|
@ -1,23 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_check.h"
|
||||
#include "mdns.h"
|
||||
#include "mdns_private.h"
|
||||
#include "mdns_networking.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
static void _mdns_browse_item_free(mdns_browse_t *browse);
|
||||
static esp_err_t _mdns_send_browse_action(mdns_action_type_t type, mdns_browse_t *browse);
|
||||
@ -255,12 +253,13 @@ static char *_mdns_mangle_name(char *in)
|
||||
}
|
||||
sprintf(ret, "%s-2", in);
|
||||
} else {
|
||||
ret = malloc(strlen(in) + 2); //one extra byte in case 9-10 or 99-100 etc
|
||||
size_t in_len = strlen(in);
|
||||
ret = malloc(in_len + 2); //one extra byte in case 9-10 or 99-100 etc
|
||||
if (ret == NULL) {
|
||||
HOOK_MALLOC_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
strcpy(ret, in);
|
||||
memcpy(ret, in, in_len);
|
||||
int baseLen = p - in; //length of 'bla' in 'bla-123'
|
||||
//overwrite suffix with new suffix
|
||||
sprintf(ret + baseLen, "-%d", suffix + 1);
|
||||
@ -334,6 +333,9 @@ static mdns_host_item_t *mdns_get_host_item(const char *hostname)
|
||||
|
||||
static bool _mdns_can_add_more_services(void)
|
||||
{
|
||||
#if MDNS_MAX_SERVICES == 0
|
||||
return false;
|
||||
#else
|
||||
mdns_srv_item_t *s = _mdns_server->services;
|
||||
uint16_t service_num = 0;
|
||||
while (s) {
|
||||
@ -343,8 +345,8 @@ static bool _mdns_can_add_more_services(void)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t _mdns_send_rx_action(mdns_rx_packet_t *packet)
|
||||
@ -749,12 +751,12 @@ static uint16_t _mdns_append_fqdn(uint8_t *packet, uint16_t *index, const char *
|
||||
//empty string so terminate
|
||||
return _mdns_append_u8(packet, index, 0);
|
||||
}
|
||||
mdns_name_t name;
|
||||
static char buf[MDNS_NAME_BUF_LEN];
|
||||
uint8_t len = strlen(strings[0]);
|
||||
//try to find first the string length in the packet (if it exists)
|
||||
uint8_t *len_location = (uint8_t *)memchr(packet, (char)len, *index);
|
||||
while (len_location) {
|
||||
mdns_name_t name;
|
||||
//check if the string after len_location is the string that we are looking for
|
||||
if (memcmp(len_location + 1, strings[0], len)) { //not continuing with our string
|
||||
search_next:
|
||||
@ -1810,7 +1812,6 @@ static bool _mdns_create_answer_from_service(mdns_tx_packet_t *packet, mdns_serv
|
||||
return false;
|
||||
}
|
||||
} else if (question->type == MDNS_TYPE_SDPTR) {
|
||||
shared = true;
|
||||
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_SDPTR, service, NULL, false, false)) {
|
||||
return false;
|
||||
}
|
||||
@ -1878,7 +1879,11 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t *parsed_
|
||||
shared = q->type == MDNS_TYPE_PTR || q->type == MDNS_TYPE_SDPTR || !parsed_packet->probe;
|
||||
if (q->type == MDNS_TYPE_SRV || q->type == MDNS_TYPE_TXT) {
|
||||
mdns_srv_item_t *service = _mdns_get_service_item_instance(q->host, q->service, q->proto, NULL);
|
||||
if (service == NULL || !_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) {
|
||||
if (service == NULL) { // Service not found, but we continue to the next question
|
||||
q = q->next;
|
||||
continue;
|
||||
}
|
||||
if (!_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) {
|
||||
_mdns_free_tx_packet(packet);
|
||||
return;
|
||||
} else {
|
||||
@ -2354,6 +2359,11 @@ static void _mdns_restart_pcb(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol
|
||||
srv_count++;
|
||||
a = a->next;
|
||||
}
|
||||
if (srv_count == 0) {
|
||||
// proble only IP
|
||||
_mdns_init_pcb_probe(tcpip_if, ip_protocol, NULL, 0, true);
|
||||
return;
|
||||
}
|
||||
mdns_srv_item_t *services[srv_count];
|
||||
size_t i = 0;
|
||||
a = _mdns_server->services;
|
||||
@ -2383,6 +2393,56 @@ static void _mdns_send_bye(mdns_srv_item_t **services, size_t len, bool include_
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send bye for particular subtypes
|
||||
*/
|
||||
static void _mdns_send_bye_subtype(mdns_srv_item_t *service, const char *instance_name, mdns_subtype_t *remove_subtypes)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < MDNS_MAX_INTERFACES; i++) {
|
||||
for (j = 0; j < MDNS_IP_PROTOCOL_MAX; j++) {
|
||||
if (mdns_is_netif_ready(i, j)) {
|
||||
mdns_tx_packet_t *packet = _mdns_alloc_packet_default((mdns_if_t)i, (mdns_ip_protocol_t)j);
|
||||
if (packet == NULL) {
|
||||
return;
|
||||
}
|
||||
packet->flags = MDNS_FLAGS_QR_AUTHORITATIVE;
|
||||
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service->service, NULL, true, true)) {
|
||||
_mdns_free_tx_packet(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t pkt[MDNS_MAX_PACKET_SIZE];
|
||||
uint16_t index = MDNS_HEAD_LEN;
|
||||
memset(pkt, 0, MDNS_HEAD_LEN);
|
||||
mdns_out_answer_t *a;
|
||||
uint8_t count;
|
||||
|
||||
_mdns_set_u16(pkt, MDNS_HEAD_FLAGS_OFFSET, packet->flags);
|
||||
_mdns_set_u16(pkt, MDNS_HEAD_ID_OFFSET, packet->id);
|
||||
|
||||
count = 0;
|
||||
a = packet->answers;
|
||||
while (a) {
|
||||
if (a->type == MDNS_TYPE_PTR && a->service) {
|
||||
const mdns_subtype_t *current_subtype = remove_subtypes;
|
||||
while (current_subtype) {
|
||||
count += (_mdns_append_subtype_ptr_record(pkt, &index, instance_name, current_subtype->subtype, a->service->service, a->service->proto, a->flush, a->bye) > 0);
|
||||
current_subtype = current_subtype->next;
|
||||
}
|
||||
}
|
||||
a = a->next;
|
||||
}
|
||||
_mdns_set_u16(pkt, MDNS_HEAD_ANSWERS_OFFSET, count);
|
||||
|
||||
_mdns_udp_pcb_write(packet->tcpip_if, packet->ip_protocol, &packet->dst, packet->port, pkt, index);
|
||||
|
||||
_mdns_free_tx_packet(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send announcement on particular PCB
|
||||
*/
|
||||
@ -2554,6 +2614,10 @@ static void _mdns_restart_all_pcbs(void)
|
||||
srv_count++;
|
||||
a = a->next;
|
||||
}
|
||||
if (srv_count == 0) {
|
||||
_mdns_probe_all_pcbs(NULL, 0, true, true);
|
||||
return;
|
||||
}
|
||||
mdns_srv_item_t *services[srv_count];
|
||||
size_t l = 0;
|
||||
a = _mdns_server->services;
|
||||
@ -2794,14 +2858,20 @@ static void _mdns_remove_scheduled_service_packets(mdns_service_t *service)
|
||||
}
|
||||
}
|
||||
|
||||
static void _mdns_free_subtype(mdns_subtype_t *subtype)
|
||||
{
|
||||
while (subtype) {
|
||||
mdns_subtype_t *next = subtype->next;
|
||||
free((char *)subtype->subtype);
|
||||
free(subtype);
|
||||
subtype = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void _mdns_free_service_subtype(mdns_service_t *service)
|
||||
{
|
||||
while (service->subtype) {
|
||||
mdns_subtype_t *next = service->subtype->next;
|
||||
free((char *)service->subtype->subtype);
|
||||
free(service->subtype);
|
||||
service->subtype = next;
|
||||
}
|
||||
_mdns_free_subtype(service->subtype);
|
||||
service->subtype = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2897,11 +2967,12 @@ static int _mdns_check_srv_collision(mdns_service_t *service, uint16_t priority,
|
||||
static int _mdns_check_txt_collision(mdns_service_t *service, const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t data_len = 0;
|
||||
if (len == 1 && service->txt) {
|
||||
if (len <= 1 && service->txt) { // len==0 means incorrect packet (and handled by the packet parser)
|
||||
// but handled here again to fix clang-tidy warning on VLA "uint8_t our[0];"
|
||||
return -1;//we win
|
||||
} else if (len > 1 && !service->txt) {
|
||||
return 1;//they win
|
||||
} else if (len == 1 && !service->txt) {
|
||||
} else if (len <= 1 && !service->txt) {
|
||||
return 0;//same
|
||||
}
|
||||
|
||||
@ -3485,8 +3556,9 @@ static void _mdns_result_txt_create(const uint8_t *data, size_t len, mdns_txt_it
|
||||
uint16_t i = 0, y;
|
||||
size_t partLen = 0;
|
||||
int num_items = _mdns_txt_items_count_get(data, len);
|
||||
if (num_items < 0) {
|
||||
return;//error
|
||||
if (num_items < 0 || num_items > SIZE_MAX / sizeof(mdns_txt_item_t)) {
|
||||
// Error: num_items is incorrect (or too large to allocate)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!num_items) {
|
||||
@ -3786,7 +3858,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
mdns_class &= 0x7FFF;
|
||||
|
||||
content = data_ptr + data_len;
|
||||
if (content > (data + len)) {
|
||||
if (content > (data + len) || data_len == 0) {
|
||||
goto clear_rx_packet;
|
||||
}
|
||||
|
||||
@ -4233,6 +4305,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
} else {
|
||||
free(out_sync_browse);
|
||||
}
|
||||
out_sync_browse = NULL;
|
||||
}
|
||||
|
||||
clear_rx_packet:
|
||||
@ -4269,15 +4342,10 @@ clear_rx_packet:
|
||||
free(record);
|
||||
}
|
||||
free(parsed_packet);
|
||||
if (browse_result_instance) {
|
||||
free(browse_result_instance);
|
||||
}
|
||||
if (browse_result_service) {
|
||||
free(browse_result_service);
|
||||
}
|
||||
if (browse_result_proto) {
|
||||
free(browse_result_proto);
|
||||
}
|
||||
free(browse_result_instance);
|
||||
free(browse_result_service);
|
||||
free(browse_result_proto);
|
||||
free(out_sync_browse);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4477,10 +4545,11 @@ void mdns_preset_if_handle_system_event(void *arg, esp_event_base_t event_base,
|
||||
case IP_EVENT_GOT_IP6: {
|
||||
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
|
||||
mdns_if_t mdns_if = _mdns_get_if_from_esp_netif(event->esp_netif);
|
||||
if (mdns_if < MDNS_MAX_INTERFACES) {
|
||||
post_mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
|
||||
post_mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
|
||||
if (mdns_if >= MDNS_MAX_INTERFACES) {
|
||||
return;
|
||||
}
|
||||
post_mdns_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);
|
||||
post_mdns_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);
|
||||
mdns_browse_t *browse = _mdns_server->browse;
|
||||
while (browse) {
|
||||
_mdns_browse_send(browse, mdns_if);
|
||||
@ -5343,7 +5412,8 @@ static void _mdns_service_task(void *pvParameters)
|
||||
for (;;) {
|
||||
if (_mdns_server && _mdns_server->action_queue) {
|
||||
if (xQueueReceive(_mdns_server->action_queue, &a, portMAX_DELAY) == pdTRUE) {
|
||||
if (a && a->type == ACTION_TASK_STOP) {
|
||||
assert(a);
|
||||
if (a->type == ACTION_TASK_STOP) {
|
||||
break;
|
||||
}
|
||||
MDNS_SERVICE_LOCK();
|
||||
@ -5892,7 +5962,7 @@ esp_err_t mdns_instance_name_set(const char *instance)
|
||||
esp_err_t mdns_service_add_for_host(const char *instance, const char *service, const char *proto, const char *host,
|
||||
uint16_t port, mdns_txt_item_t txt[], size_t num_items)
|
||||
{
|
||||
if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto) || !port || !_mdns_server->hostname) {
|
||||
if (!_mdns_server || _str_null_or_empty(service) || _str_null_or_empty(proto) || !_mdns_server->hostname) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
@ -5901,7 +5971,8 @@ esp_err_t mdns_service_add_for_host(const char *instance, const char *service, c
|
||||
const char *hostname = host ? host : _mdns_server->hostname;
|
||||
mdns_service_t *s = NULL;
|
||||
|
||||
ESP_GOTO_ON_FALSE(_mdns_can_add_more_services(), ESP_ERR_NO_MEM, err, TAG, "Cannot add more services");
|
||||
ESP_GOTO_ON_FALSE(_mdns_can_add_more_services(), ESP_ERR_NO_MEM, err, TAG,
|
||||
"Cannot add more services, please increase CONFIG_MDNS_MAX_SERVICES (%d)", CONFIG_MDNS_MAX_SERVICES);
|
||||
|
||||
mdns_srv_item_t *item = _mdns_get_service_item_instance(instance, service, proto, hostname);
|
||||
ESP_GOTO_ON_FALSE(!item, ESP_ERR_INVALID_ARG, err, TAG, "Service already exists");
|
||||
@ -6347,11 +6418,23 @@ esp_err_t mdns_service_subtype_remove_for_host(const char *instance_name, const
|
||||
ret = _mdns_service_subtype_remove_for_host(s, subtype);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to remove the subtype: %s", subtype);
|
||||
|
||||
// TODO: Need to transmit a sendbye message for the removed subtype.
|
||||
// TODO: Need to remove this subtype answer from the scheduled answer list.
|
||||
// Transmit a sendbye message for the removed subtype.
|
||||
mdns_subtype_t *remove_subtypes = (mdns_subtype_t *)malloc(sizeof(mdns_subtype_t));
|
||||
ESP_GOTO_ON_FALSE(remove_subtypes, ESP_ERR_NO_MEM, out_of_mem, TAG, "Out of memory");
|
||||
remove_subtypes->subtype = strdup(subtype);
|
||||
ESP_GOTO_ON_FALSE(remove_subtypes->subtype, ESP_ERR_NO_MEM, out_of_mem, TAG, "Out of memory");
|
||||
remove_subtypes->next = NULL;
|
||||
|
||||
_mdns_send_bye_subtype(s, instance_name, remove_subtypes);
|
||||
_mdns_free_subtype(remove_subtypes);
|
||||
err:
|
||||
MDNS_SERVICE_UNLOCK();
|
||||
return ret;
|
||||
out_of_mem:
|
||||
HOOK_MALLOC_FAILED;
|
||||
free(remove_subtypes);
|
||||
MDNS_SERVICE_UNLOCK();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t _mdns_service_subtype_add_for_host(mdns_srv_item_t *service, const char *subtype)
|
||||
@ -6423,19 +6506,75 @@ esp_err_t mdns_service_subtype_add_for_host(const char *instance_name, const cha
|
||||
return mdns_service_subtype_add_multiple_items_for_host(instance_name, service_type, proto, hostname, _subtype, 1);
|
||||
}
|
||||
|
||||
static mdns_subtype_t *_mdns_service_find_subtype_needed_sendbye(mdns_service_t *service, mdns_subtype_item_t subtype[],
|
||||
uint8_t num_items)
|
||||
{
|
||||
if (!service) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mdns_subtype_t *current = service->subtype;
|
||||
mdns_subtype_t *prev = NULL;
|
||||
mdns_subtype_t *prev_goodbye = NULL;
|
||||
mdns_subtype_t *out_goodbye_subtype = NULL;
|
||||
|
||||
while (current) {
|
||||
bool subtype_in_update = false;
|
||||
|
||||
for (int i = 0; i < num_items; i++) {
|
||||
if (strcmp(subtype[i].subtype, current->subtype) == 0) {
|
||||
subtype_in_update = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!subtype_in_update) {
|
||||
// Remove from original list
|
||||
if (prev) {
|
||||
prev->next = current->next;
|
||||
} else {
|
||||
service->subtype = current->next;
|
||||
}
|
||||
|
||||
mdns_subtype_t *to_move = current;
|
||||
current = current->next;
|
||||
|
||||
// Add to goodbye list
|
||||
to_move->next = NULL;
|
||||
if (prev_goodbye) {
|
||||
prev_goodbye->next = to_move;
|
||||
} else {
|
||||
out_goodbye_subtype = to_move;
|
||||
}
|
||||
prev_goodbye = to_move;
|
||||
} else {
|
||||
prev = current;
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
return out_goodbye_subtype;
|
||||
}
|
||||
|
||||
esp_err_t mdns_service_subtype_update_multiple_items_for_host(const char *instance_name, const char *service_type, const char *proto,
|
||||
const char *hostname, mdns_subtype_item_t subtype[], uint8_t num_items)
|
||||
{
|
||||
MDNS_SERVICE_LOCK();
|
||||
esp_err_t ret = ESP_OK;
|
||||
int cur_index = 0;
|
||||
ESP_GOTO_ON_FALSE(_mdns_server && _mdns_server->services && !_str_null_or_empty(service_type) && !_str_null_or_empty(proto) &&
|
||||
(num_items > 0), ESP_ERR_INVALID_ARG, err, TAG, "Invalid state or arguments");
|
||||
ESP_GOTO_ON_FALSE(_mdns_server && _mdns_server->services && !_str_null_or_empty(service_type) && !_str_null_or_empty(proto),
|
||||
ESP_ERR_INVALID_ARG, err, TAG, "Invalid state or arguments");
|
||||
|
||||
mdns_srv_item_t *s = _mdns_get_service_item_instance(instance_name, service_type, proto, hostname);
|
||||
ESP_GOTO_ON_FALSE(s, ESP_ERR_NOT_FOUND, err, TAG, "Service doesn't exist");
|
||||
|
||||
// TODO: find subtype needs to say sendbye
|
||||
mdns_subtype_t *goodbye_subtype = _mdns_service_find_subtype_needed_sendbye(s->service, subtype, num_items);
|
||||
|
||||
if (goodbye_subtype) {
|
||||
_mdns_send_bye_subtype(s, instance_name, goodbye_subtype);
|
||||
}
|
||||
|
||||
_mdns_free_subtype(goodbye_subtype);
|
||||
_mdns_free_service_subtype(s->service);
|
||||
|
||||
for (; cur_index < num_items; cur_index++) {
|
||||
@ -6450,8 +6589,9 @@ esp_err_t mdns_service_subtype_update_multiple_items_for_host(const char *instan
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
_mdns_announce_all_pcbs(&s, 1, false);
|
||||
if (num_items) {
|
||||
_mdns_announce_all_pcbs(&s, 1, false);
|
||||
}
|
||||
err:
|
||||
if (ret == ESP_ERR_NO_MEM) {
|
||||
for (int idx = 0; idx < cur_index; idx++) {
|
||||
|
@ -288,7 +288,7 @@ typedef struct {
|
||||
static err_t _mdns_pcb_init_api(struct tcpip_api_call_data *api_call_msg)
|
||||
{
|
||||
mdns_api_call_t *msg = (mdns_api_call_t *)api_call_msg;
|
||||
msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol);
|
||||
msg->err = _udp_pcb_init(msg->tcpip_if, msg->ip_protocol) == ESP_OK ? ERR_OK : ERR_IF;
|
||||
return msg->err;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -103,12 +103,6 @@
|
||||
#define MDNS_PACKET_QUEUE_LEN 16 // Maximum packets that can be queued for parsing
|
||||
#define MDNS_ACTION_QUEUE_LEN CONFIG_MDNS_ACTION_QUEUE_LEN // Maximum actions pending to the server
|
||||
#define MDNS_TXT_MAX_LEN 1024 // Maximum string length of text data in TXT record
|
||||
#if defined(CONFIG_LWIP_IPV6) && defined(CONFIG_MDNS_RESPOND_REVERSE_QUERIES)
|
||||
#define MDNS_NAME_MAX_LEN (64+4) // Need to account for IPv6 reverse queries (64 char address + ".ip6" )
|
||||
#else
|
||||
#define MDNS_NAME_MAX_LEN 64 // Maximum string length of hostname, instance, service and proto
|
||||
#endif
|
||||
#define MDNS_NAME_BUF_LEN (MDNS_NAME_MAX_LEN+1) // Maximum char buffer size to hold hostname, instance, service or proto
|
||||
#define MDNS_MAX_PACKET_SIZE 1460 // Maximum size of mDNS outgoing packet
|
||||
|
||||
#define MDNS_HEAD_LEN 12
|
||||
|
@ -117,6 +117,10 @@ void esp_log_write(esp_log_level_t level, const char *tag, const char *format, .
|
||||
{
|
||||
}
|
||||
|
||||
void esp_log(esp_log_config_t config, const char *tag, const char *format, ...)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t esp_log_timestamp(void)
|
||||
{
|
||||
return 0;
|
||||
|
3
components/mosquitto/.build-test-rules.yml
Normal file
3
components/mosquitto/.build-test-rules.yml
Normal file
@ -0,0 +1,3 @@
|
||||
components/mosquitto/examples/serverless_mqtt:
|
||||
disable:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32s3", "esp32c3"]
|
@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(mosq): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py mosquitto
|
||||
tag_format: mosq-v$version
|
||||
version: 2.0.28~0
|
||||
version: 2.0.20~1
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,30 @@
|
||||
# Changelog
|
||||
|
||||
## [2.0.20~1](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.20_1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Use sock_utils instead of func stubs ([3cd0ed37](https://github.com/espressif/esp-protocols/commit/3cd0ed37))
|
||||
- Update API docs adding on-message callback ([5dcc3330](https://github.com/espressif/esp-protocols/commit/5dcc3330))
|
||||
|
||||
## [2.0.20](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.20)
|
||||
|
||||
### Features
|
||||
|
||||
- Upgrade to mosquitto v2.0.20 ([3b2c614d](https://github.com/espressif/esp-protocols/commit/3b2c614d))
|
||||
- Add support for on-message callback ([cdeab8f5](https://github.com/espressif/esp-protocols/commit/cdeab8f5))
|
||||
- Add example with two brokers synced on P2P ([d57b8c5b](https://github.com/espressif/esp-protocols/commit/d57b8c5b))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix dependency issues moving esp-tls to public deps ([6cce87e4](https://github.com/espressif/esp-protocols/commit/6cce87e4))
|
||||
|
||||
## [2.0.28~0](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.28_0)
|
||||
|
||||
### Warning
|
||||
|
||||
Incorrect version number! This version published under `2.0.28~0` is based on upstream v2.0.18
|
||||
|
||||
### Features
|
||||
|
||||
- Added support for TLS transport using ESP-TLS ([1af4bbe1](https://github.com/espressif/esp-protocols/commit/1af4bbe1))
|
||||
|
@ -74,15 +74,15 @@ idf_component_register(SRCS ${m_srcs}
|
||||
port/callbacks.c
|
||||
port/config.c
|
||||
port/signals.c
|
||||
port/ifaddrs.c
|
||||
port/broker.c
|
||||
port/files.c
|
||||
port/net__esp_tls.c
|
||||
port/sysconf.c
|
||||
PRIV_INCLUDE_DIRS port/priv_include port/priv_include/sys ${m_dir} ${m_src_dir}
|
||||
${m_incl_dir} ${m_lib_dir} ${m_deps_dir}
|
||||
INCLUDE_DIRS ${m_incl_dir} port/include
|
||||
PRIV_REQUIRES newlib esp-tls
|
||||
)
|
||||
REQUIRES esp-tls
|
||||
PRIV_REQUIRES newlib sock_utils)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "WITH_BROKER")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
||||
|
@ -20,7 +20,7 @@ mosq_broker_run(&config);
|
||||
|
||||
## Memory Footprint Considerations
|
||||
|
||||
The broker primarily uses the heap for internal data, with minimal use of static/BSS memory. It consumes approximately 60 kB of program memory.
|
||||
The broker primarily uses the heap for internal data, with minimal use of static/BSS memory. It consumes approximately 60 kB of program memory and minimum 5kB of stack size.
|
||||
|
||||
- **Initial Memory Usage**: ~2 kB of heap on startup
|
||||
- **Per Client Memory Usage**: ~4 kB of heap for each connected client
|
||||
|
@ -15,6 +15,7 @@
|
||||
| Type | Name |
|
||||
| ---: | :--- |
|
||||
| struct | [**mosq\_broker\_config**](#struct-mosq_broker_config) <br>_Mosquitto configuration structure._ |
|
||||
| typedef void(\* | [**mosq\_message\_cb\_t**](#typedef-mosq_message_cb_t) <br> |
|
||||
|
||||
## Functions
|
||||
|
||||
@ -34,12 +35,20 @@ ESP port of mosquittto supports only the options in this configuration structure
|
||||
|
||||
Variables:
|
||||
|
||||
- void(\* handle_message_cb <br>On message callback. If configured, user function is called whenever mosquitto processes a message.
|
||||
|
||||
- char \* host <br>Address on which the broker is listening for connections
|
||||
|
||||
- int port <br>Port number of the broker to listen to
|
||||
|
||||
- esp\_tls\_cfg\_server\_t \* tls_cfg <br>ESP-TLS configuration (if TLS transport used) Please refer to the ESP-TLS official documentation for more details on configuring the TLS options. You can open the respective docs with this idf.py command: `idf.py docs -sp api-reference/protocols/esp_tls.html`
|
||||
|
||||
### typedef `mosq_message_cb_t`
|
||||
|
||||
```c
|
||||
typedef void(* mosq_message_cb_t) (char *client, char *topic, char *data, int len, int qos, int retain);
|
||||
```
|
||||
|
||||
|
||||
## Functions Documentation
|
||||
|
||||
|
@ -0,0 +1,6 @@
|
||||
# The following five 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(serverless_mqtt)
|
53
components/mosquitto/examples/serverless_mqtt/README.md
Normal file
53
components/mosquitto/examples/serverless_mqtt/README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Brokerless MQTT Example
|
||||
|
||||
MQTT served by (two) mosquitto's running on two ESP chips.
|
||||
|
||||
* Leverages MQTT connectivity between two private networks without cloud premisses.
|
||||
* Creates two local MQTT servers (on ESP32x's) which are being synchronized over peer to peer connection (established via ICE protocol, by [libjuice](https://github.com/paullouisageneau/libjuice)).
|
||||
|
||||
## How it works
|
||||
|
||||
This example needs two ESP32 chipsets, that will create two separate Wi-Fi networks (IoT networks) used for IoT devices.
|
||||
Each IoT network is served by an MQTT server (using mosquitto component).
|
||||
This example will also synchronize these two MQTT brokers, as if there was only one IoT network with one broker.
|
||||
This example creates a peer to peer connection between two chipsets to keep them synchronize. This connection utilizes libjuice (which implements a simplified ICE-UDP) to traverse NATs, which enabling direct connection between two private networks behind NATs.
|
||||
|
||||
* Diagram
|
||||
|
||||

|
||||
|
||||
Here's a step-by-step procedure of establishing this remote connection:
|
||||
1) Initialize and start Wi-Fi AP (for IoT networks) and Wi-Fi station (for internet connection)
|
||||
2) Start mosquitto broker on IoT network
|
||||
3) Start libjuice to gather connection candidates
|
||||
4) Synchronize using a public MQTT broker and exchange ICE descriptors
|
||||
5) Establish ICE UDP connection between the two ESP32 chipsets
|
||||
6) Start forwarding mqtt messages
|
||||
- Each remote datagram (received from ICE-UDP channel) is re-published to the local MQTT server
|
||||
- Each local MQTT message (received from mosquitto on_message callback) is sent in ICE-UDP datagram
|
||||
|
||||
## How to use this example
|
||||
|
||||
You need two ESP32 devices that support Wi-Fi station and Wi-Fi software access point.
|
||||
|
||||
* Configure Wi-Fi credentials for both devices on both interfaces
|
||||
* These devices would be deployed in distinct Wi-Fi environments, so the Wi-Fi station credentials would likely be different.
|
||||
* They also create their own IoT network (on the soft-AP interface) Wi-Fi, so the AP credentials would likely be the same, suggesting the IoT networks will be keep synchronized (even though these are two distict Wi-Fi networks).
|
||||
* Choose `CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1` for one device and `CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER2` for another. It's not important which device is PEER1, since the code is symmetric, but these two devices need to have different role.
|
||||
* Optionally: You can use `idf.py` `-D` and `-B` flag to keep separate build directories and sdkconfigs for these two roles
|
||||
```
|
||||
idf.py -B build1 -DSDKCONFIG=build1/sdkconfig menuconfig build flash monitor
|
||||
```
|
||||
* Flash and run the two devices and wait for them to connect and synchronize.
|
||||
* Now you can test MQTT connectivity, for example:
|
||||
* Join PEER1 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
|
||||
* Join PEER2 device's AP and connect to the MQTT broker with one or more clients, subscribing to one or more topics.
|
||||
* Whenever you publish to a topic, all subscribed clients should receive the message, no matter which Wi-Fi network they're connected to.
|
||||
|
||||
## Warning
|
||||
|
||||
This example uses libjuice as a dependency:
|
||||
|
||||
* libjuice (UDP Interactive Connectivity Establishment): https://github.com/paullouisageneau/libjuice
|
||||
|
||||
which is distributed under Mozilla Public License v2.0.
|
@ -0,0 +1,44 @@
|
||||
set(LIBJUICE_VERSION "73785387eafe15c02b6a210edb10f722474e8e14")
|
||||
set(LIBJUICE_URL "https://github.com/paullouisageneau/libjuice/archive/${LIBJUICE_VERSION}.zip")
|
||||
|
||||
set(libjuice_dir ${CMAKE_BINARY_DIR}/libjuice/libjuice-${LIBJUICE_VERSION})
|
||||
|
||||
# Fetch the library
|
||||
if(NOT EXISTS ${libjuice_dir})
|
||||
message(STATUS "Downloading libjuice ${LIBJUICE_VERSION}...")
|
||||
file(DOWNLOAD ${LIBJUICE_URL} ${CMAKE_BINARY_DIR}/libjuice.zip SHOW_PROGRESS)
|
||||
execute_process(COMMAND unzip -o ${CMAKE_BINARY_DIR}/libjuice.zip -d ${CMAKE_BINARY_DIR}/libjuice
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
set(JUICE_SOURCES ${libjuice_dir}/src/addr.c
|
||||
${libjuice_dir}/src/agent.c
|
||||
${libjuice_dir}/src/base64.c
|
||||
${libjuice_dir}/src/conn.c
|
||||
${libjuice_dir}/src/conn_mux.c
|
||||
${libjuice_dir}/src/conn_poll.c
|
||||
${libjuice_dir}/src/conn_thread.c
|
||||
${libjuice_dir}/src/const_time.c
|
||||
${libjuice_dir}/src/crc32.c
|
||||
${libjuice_dir}/src/hash.c
|
||||
${libjuice_dir}/src/ice.c
|
||||
${libjuice_dir}/src/juice.c
|
||||
${libjuice_dir}/src/log.c
|
||||
${libjuice_dir}/src/server.c
|
||||
${libjuice_dir}/src/stun.c
|
||||
${libjuice_dir}/src/timestamp.c
|
||||
${libjuice_dir}/src/turn.c
|
||||
${libjuice_dir}/src/udp.c
|
||||
# Use hmac from mbedtls and random numbers from esp_random:
|
||||
# ${libjuice_dir}/src/hmac.c
|
||||
# ${libjuice_dir}/src/random.c
|
||||
)
|
||||
|
||||
idf_component_register(SRCS port/juice_random.c
|
||||
${JUICE_SOURCES}
|
||||
INCLUDE_DIRS "include" "${libjuice_dir}/include" "${libjuice_dir}/include/juice"
|
||||
REQUIRES esp_netif
|
||||
PRIV_REQUIRES sock_utils)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
|
||||
set_source_files_properties(${libjuice_dir}/src/udp.c PROPERTIES COMPILE_FLAGS -Wno-unused-variable)
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// Purpose of this header is to replace udp_sendto() to avoid name conflict with lwip
|
||||
// added here since ifaddrs.h is included from juice_udp sources
|
||||
#define udp_sendto juice_udp_sendto
|
||||
|
||||
// other than that, let's just include the ifaddrs (from sock_utils)
|
||||
#include_next "ifaddrs.h"
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Paul-Louis Ageneau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
#include "esp_random.h"
|
||||
|
||||
void juice_random(void *buf, size_t size)
|
||||
{
|
||||
esp_fill_random(buf, size);
|
||||
}
|
||||
|
||||
void juice_random_str64(char *buf, size_t size)
|
||||
{
|
||||
static const char chars64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
size_t i = 0;
|
||||
for (i = 0; i + 1 < size; ++i) {
|
||||
uint8_t byte = 0;
|
||||
juice_random(&byte, 1);
|
||||
buf[i] = chars64[byte & 0x3F];
|
||||
}
|
||||
buf[i] = '\0';
|
||||
}
|
||||
|
||||
uint32_t juice_rand32(void)
|
||||
{
|
||||
uint32_t r = 0;
|
||||
juice_random(&r, sizeof(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t juice_rand64(void)
|
||||
{
|
||||
uint64_t r = 0;
|
||||
juice_random(&r, sizeof(r));
|
||||
return r;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "serverless_mqtt.c"
|
||||
"wifi_connect.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES libjuice nvs_flash mqtt json esp_wifi)
|
@ -0,0 +1,85 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
menu "AP Configuration"
|
||||
comment "AP Configuration"
|
||||
|
||||
config EXAMPLE_AP_SSID
|
||||
string "Wi-Fi SSID"
|
||||
default "myssid"
|
||||
help
|
||||
Set the SSID of Wi-Fi ap interface.
|
||||
|
||||
config EXAMPLE_AP_PASSWORD
|
||||
string "Wi-Fi Password"
|
||||
default "12345678"
|
||||
help
|
||||
Set the password of Wi-Fi ap interface.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "STA Configuration"
|
||||
comment "STA Configuration"
|
||||
|
||||
config EXAMPLE_STA_SSID
|
||||
string "WiFi Station SSID"
|
||||
default "mystationssid"
|
||||
help
|
||||
SSID for the example's sta to connect to.
|
||||
|
||||
config EXAMPLE_STA_PASSWORD
|
||||
string "WiFi Station Password"
|
||||
default "mystationpassword"
|
||||
help
|
||||
WiFi station password for the example to use.
|
||||
endmenu
|
||||
|
||||
config EXAMPLE_MQTT_BROKER_URI
|
||||
string "MQTT Broker URL"
|
||||
default "mqtt://mqtt.eclipseprojects.io"
|
||||
help
|
||||
URL of the mqtt broker use for synchronisation and exchanging
|
||||
ICE connect info (description and candidates).
|
||||
|
||||
config EXAMPLE_MQTT_SYNC_TOPIC
|
||||
string "MQTT topic for synchronisation"
|
||||
default "/topic/serverless_mqtt"
|
||||
help
|
||||
MQTT topic used fo synchronisation.
|
||||
|
||||
config EXAMPLE_STUN_SERVER
|
||||
string "Hostname of STUN server"
|
||||
default "stun.l.google.com"
|
||||
help
|
||||
STUN server hostname.
|
||||
|
||||
config EXAMPLE_MQTT_CLIENT_STACK_SIZE
|
||||
int "Stack size for mqtt client"
|
||||
default 16384
|
||||
help
|
||||
Set stack size for the mqtt client.
|
||||
Need more stack, since calling juice API from the handler.
|
||||
|
||||
config EXAMPLE_MQTT_BROKER_PORT
|
||||
int "port for the mosquitto to listen to"
|
||||
default 1883
|
||||
help
|
||||
This is a port which the local mosquitto uses.
|
||||
|
||||
choice EXAMPLE_SERVERLESS_ROLE
|
||||
prompt "Choose your role"
|
||||
default EXAMPLE_SERVERLESS_ROLE_PEER1
|
||||
help
|
||||
Choose either peer1 or peer2.
|
||||
It's not very important which device is peer1
|
||||
(peer-1 sends sync messages, peer2 listens for them)
|
||||
It is important that we have two peers,
|
||||
one with peer1 config, another one with peer2 config
|
||||
|
||||
config EXAMPLE_SERVERLESS_ROLE_PEER1
|
||||
bool "peer1"
|
||||
|
||||
config EXAMPLE_SERVERLESS_ROLE_PEER2
|
||||
bool "peer2"
|
||||
endchoice
|
||||
|
||||
endmenu
|
@ -0,0 +1,5 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/mosquitto:
|
||||
override_path: ../../..
|
||||
espressif/sock_utils: "*"
|
@ -0,0 +1,374 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "mqtt_client.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "mosq_broker.h"
|
||||
#include "juice/juice.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
#if defined(CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1)
|
||||
#define OUR_PEER "1"
|
||||
#define THEIR_PEER "2"
|
||||
#elif defined(CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER2)
|
||||
#define OUR_PEER "2"
|
||||
#define THEIR_PEER "1"
|
||||
#endif
|
||||
|
||||
#define PEER_SYNC0 BIT(0)
|
||||
#define PEER_SYNC1 BIT(1)
|
||||
#define PEER_SYNC2 BIT(2)
|
||||
#define PEER_FAIL BIT(3)
|
||||
#define PEER_GATHER_DONE BIT(4)
|
||||
#define PEER_DESC_PUBLISHED BIT(5)
|
||||
#define PEER_CONNECTED BIT(6)
|
||||
|
||||
#define SYNC_BITS (PEER_SYNC1 | PEER_SYNC2 | PEER_FAIL)
|
||||
|
||||
#define PUBLISH_SYNC_TOPIC CONFIG_EXAMPLE_MQTT_SYNC_TOPIC OUR_PEER
|
||||
#define SUBSCRIBE_SYNC_TOPIC CONFIG_EXAMPLE_MQTT_SYNC_TOPIC THEIR_PEER
|
||||
#define MAX_BUFFER_SIZE JUICE_MAX_SDP_STRING_LEN
|
||||
|
||||
typedef struct message_wrap {
|
||||
uint16_t topic_len;
|
||||
uint16_t data_len;
|
||||
char data[];
|
||||
} __attribute__((packed)) message_wrap_t;
|
||||
|
||||
static const char *TAG = "serverless_mqtt" OUR_PEER;
|
||||
static char s_buffer[MAX_BUFFER_SIZE];
|
||||
static EventGroupHandle_t s_state = NULL;
|
||||
static juice_agent_t *s_agent = NULL;
|
||||
static cJSON *s_peer_desc_json = NULL;
|
||||
static char *s_peer_desc = NULL;
|
||||
static esp_mqtt_client_handle_t s_local_mqtt = NULL;
|
||||
|
||||
char *wifi_get_ipv4(wifi_interface_t interface);
|
||||
esp_err_t wifi_connect(void);
|
||||
static esp_err_t sync_peers(void);
|
||||
static esp_err_t create_candidates(void);
|
||||
static esp_err_t create_local_client(void);
|
||||
static esp_err_t create_local_broker(void);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
__attribute__((__unused__)) esp_err_t ret;
|
||||
ESP_GOTO_ON_ERROR(wifi_connect(), err, TAG, "Failed to initialize WiFi");
|
||||
ESP_GOTO_ON_ERROR(create_local_broker(), err, TAG, "Failed to create local broker");
|
||||
ESP_GOTO_ON_ERROR(create_candidates(), err, TAG, "Failed to create juice candidates");
|
||||
ESP_GOTO_ON_ERROR(sync_peers(), err, TAG, "Failed to sync with the other peer");
|
||||
EventBits_t bits = xEventGroupWaitBits(s_state, PEER_FAIL | PEER_CONNECTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(90000));
|
||||
if (bits & PEER_CONNECTED) {
|
||||
ESP_LOGI(TAG, "Peer is connected!");
|
||||
ESP_GOTO_ON_ERROR(create_local_client(), err, TAG, "Failed to create forwarding mqtt client");
|
||||
ESP_LOGI(TAG, "Everything is ready, exiting main task");
|
||||
return;
|
||||
}
|
||||
err:
|
||||
ESP_LOGE(TAG, "Non recoverable error, going to sleep for some time (random, max 20s)");
|
||||
esp_deep_sleep(1000000LL * (esp_random() % 20));
|
||||
}
|
||||
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||
{
|
||||
esp_mqtt_event_handle_t event = event_data;
|
||||
esp_mqtt_client_handle_t client = event->client;
|
||||
switch ((esp_mqtt_event_id_t)event_id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
if (esp_mqtt_client_subscribe(client, SUBSCRIBE_SYNC_TOPIC, 1) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to subscribe to the sync topic");
|
||||
}
|
||||
xEventGroupSetBits(s_state, PEER_SYNC0);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
xEventGroupSetBits(s_state, PEER_FAIL);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DATA:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
|
||||
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||||
printf("DATA=%.*s\r\n", event->data_len, event->data);
|
||||
if (s_state == NULL || memcmp(event->topic, SUBSCRIBE_SYNC_TOPIC, event->topic_len) != 0) {
|
||||
break;
|
||||
}
|
||||
EventBits_t bits = xEventGroupGetBits(s_state);
|
||||
if (event->data_len > 1 && s_agent) {
|
||||
cJSON *root = cJSON_Parse(event->data);
|
||||
if (root == NULL) {
|
||||
break;
|
||||
}
|
||||
cJSON *desc = cJSON_GetObjectItem(root, "desc");
|
||||
if (desc == NULL) {
|
||||
cJSON_Delete(root);
|
||||
break;
|
||||
}
|
||||
printf("desc->valuestring:%s\n", desc->valuestring);
|
||||
juice_set_remote_description(s_agent, desc->valuestring);
|
||||
char cand_name[] = "cand0";
|
||||
while (true) {
|
||||
cJSON *cand = cJSON_GetObjectItem(root, cand_name);
|
||||
if (cand == NULL) {
|
||||
break;
|
||||
}
|
||||
printf("%s: cand->valuestring:%s\n", cand_name, cand->valuestring);
|
||||
juice_add_remote_candidate(s_agent, cand->valuestring);
|
||||
cand_name[4]++;
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
xEventGroupSetBits(s_state, PEER_DESC_PUBLISHED); // this will complete the sync process
|
||||
// and destroy the mqtt client
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1
|
||||
if (event->data_len == 1 && event->data[0] == '1' && (bits & PEER_SYNC2) == 0) {
|
||||
if (esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "2", 1, 1, 0) >= 0) {
|
||||
xEventGroupSetBits(s_state, PEER_SYNC2);
|
||||
} else {
|
||||
xEventGroupSetBits(s_state, PEER_FAIL);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (event->data_len == 1 && event->data[0] == '0' && (bits & PEER_SYNC1) == 0) {
|
||||
if (esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "1", 1, 1, 0) >= 0) {
|
||||
xEventGroupSetBits(s_state, PEER_SYNC1);
|
||||
} else {
|
||||
xEventGroupSetBits(s_state, PEER_FAIL);
|
||||
}
|
||||
} else if (event->data_len == 1 && event->data[0] == '2' && (bits & PEER_SYNC2) == 0) {
|
||||
xEventGroupSetBits(s_state, PEER_SYNC2);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
|
||||
xEventGroupSetBits(s_state, PEER_FAIL);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t sync_peers(void)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_mqtt_client_config_t mqtt_cfg = {
|
||||
.broker.address.uri = CONFIG_EXAMPLE_MQTT_BROKER_URI,
|
||||
.task.stack_size = CONFIG_EXAMPLE_MQTT_CLIENT_STACK_SIZE,
|
||||
};
|
||||
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg);
|
||||
ESP_GOTO_ON_FALSE(client, ESP_ERR_NO_MEM, err, TAG, "Failed to create mqtt client");
|
||||
ESP_GOTO_ON_ERROR(esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL),
|
||||
err, TAG, "Failed to register mqtt event handler");
|
||||
ESP_GOTO_ON_ERROR(esp_mqtt_client_start(client), err, TAG, "Failed to start mqtt client");
|
||||
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_SYNC0, pdTRUE, pdTRUE, pdMS_TO_TICKS(10000)),
|
||||
ESP_FAIL, err, TAG, "Failed to connect to the sync broker");
|
||||
ESP_LOGI(TAG, "Waiting for the other peer...");
|
||||
const int max_sync_retry = 60;
|
||||
int retry = 0;
|
||||
while (true) {
|
||||
EventBits_t bits = xEventGroupWaitBits(s_state, SYNC_BITS, pdTRUE, pdFALSE, pdMS_TO_TICKS(1000));
|
||||
if (bits & PEER_SYNC2) {
|
||||
break;
|
||||
}
|
||||
if (bits & PEER_SYNC1) {
|
||||
continue;
|
||||
}
|
||||
ESP_GOTO_ON_FALSE((bits & PEER_FAIL) == 0, ESP_FAIL, err, TAG, "Failed to sync with the other peer");
|
||||
ESP_GOTO_ON_FALSE(retry++ < max_sync_retry, ESP_FAIL, err, TAG, "Failed to sync after %d seconds", retry);
|
||||
#ifdef CONFIG_EXAMPLE_SERVERLESS_ROLE_PEER1
|
||||
ESP_RETURN_ON_FALSE(esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, "0", 1, 1, 0) >= 0,
|
||||
ESP_FAIL, TAG, "Failed to publish mqtt message");
|
||||
#endif
|
||||
}
|
||||
ESP_LOGI(TAG, "Sync done");
|
||||
ESP_RETURN_ON_FALSE(esp_mqtt_client_publish(client, PUBLISH_SYNC_TOPIC, s_peer_desc, 0, 1, 0) >= 0,
|
||||
ESP_FAIL, TAG, "Failed to publish peer's description");
|
||||
ESP_LOGI(TAG, "Waiting for the other peer description and candidates...");
|
||||
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_DESC_PUBLISHED, pdTRUE, pdTRUE, pdMS_TO_TICKS(10000)),
|
||||
ESP_FAIL, err, TAG, "Timeout in waiting for the other peer candidates");
|
||||
err:
|
||||
free(s_peer_desc);
|
||||
esp_mqtt_client_destroy(client);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void juice_state(juice_agent_t *agent, juice_state_t state, void *user_ptr)
|
||||
{
|
||||
ESP_LOGI(TAG, "JUICE state change: %s", juice_state_to_string(state));
|
||||
if (state == JUICE_STATE_CONNECTED) {
|
||||
xEventGroupSetBits(s_state, PEER_CONNECTED);
|
||||
} else if (state == JUICE_STATE_FAILED || state == JUICE_STATE_DISCONNECTED) {
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
|
||||
static void juice_candidate(juice_agent_t *agent, const char *sdp, void *user_ptr)
|
||||
{
|
||||
static uint8_t cand_nr = 0;
|
||||
if (s_peer_desc_json && cand_nr < 10) { // supporting only 10 candidates
|
||||
char cand_name[] = "cand0";
|
||||
cand_name[4] += cand_nr++;
|
||||
cJSON_AddStringToObject(s_peer_desc_json, cand_name, sdp);
|
||||
}
|
||||
}
|
||||
|
||||
static void juice_gathering_done(juice_agent_t *agent, void *user_ptr)
|
||||
{
|
||||
ESP_LOGI(TAG, "Gathering done");
|
||||
if (s_state) {
|
||||
xEventGroupSetBits(s_state, PEER_GATHER_DONE);
|
||||
}
|
||||
}
|
||||
|
||||
#define ALIGN(size) (((size) + 3U) & ~(3U))
|
||||
|
||||
static void juice_recv(juice_agent_t *agent, const char *data, size_t size, void *user_ptr)
|
||||
{
|
||||
if (s_local_mqtt) {
|
||||
message_wrap_t *message = (message_wrap_t *)data;
|
||||
int topic_len = message->topic_len;
|
||||
int payload_len = message->data_len;
|
||||
int topic_len_aligned = ALIGN(topic_len);
|
||||
char *topic = message->data;
|
||||
char *payload = message->data + topic_len_aligned;
|
||||
if (topic_len + topic_len_aligned + 4 > size) {
|
||||
ESP_LOGE(TAG, "Received invalid message");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "forwarding remote message: topic:%s", topic);
|
||||
ESP_LOGI(TAG, "forwarding remote message: payload:%.*s", payload_len, payload);
|
||||
esp_mqtt_client_publish(s_local_mqtt, topic, payload, payload_len, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t create_candidates(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_state = xEventGroupCreate(), ESP_ERR_NO_MEM, TAG, "Failed to create state event group");
|
||||
s_peer_desc_json = cJSON_CreateObject();
|
||||
esp_err_t ret = ESP_OK;
|
||||
juice_set_log_level(JUICE_LOG_LEVEL_INFO);
|
||||
juice_config_t config = { .stun_server_host = CONFIG_EXAMPLE_STUN_SERVER,
|
||||
.bind_address = wifi_get_ipv4(WIFI_IF_STA),
|
||||
.stun_server_port = 19302,
|
||||
.cb_state_changed = juice_state,
|
||||
.cb_candidate = juice_candidate,
|
||||
.cb_gathering_done = juice_gathering_done,
|
||||
.cb_recv = juice_recv,
|
||||
};
|
||||
|
||||
s_agent = juice_create(&config);
|
||||
ESP_RETURN_ON_FALSE(s_agent, ESP_FAIL, TAG, "Failed to create juice agent");
|
||||
ESP_GOTO_ON_FALSE(juice_get_local_description(s_agent, s_buffer, MAX_BUFFER_SIZE) == JUICE_ERR_SUCCESS,
|
||||
ESP_FAIL, err, TAG, "Failed to get local description");
|
||||
ESP_LOGI(TAG, "desc: %s", s_buffer);
|
||||
cJSON_AddStringToObject(s_peer_desc_json, "desc", s_buffer);
|
||||
|
||||
ESP_GOTO_ON_FALSE(juice_gather_candidates(s_agent) == JUICE_ERR_SUCCESS,
|
||||
ESP_FAIL, err, TAG, "Failed to start gathering candidates");
|
||||
ESP_GOTO_ON_FALSE(xEventGroupWaitBits(s_state, PEER_GATHER_DONE, pdTRUE, pdTRUE, pdMS_TO_TICKS(30000)),
|
||||
ESP_FAIL, err, TAG, "Failed to connect to the sync broker");
|
||||
s_peer_desc = cJSON_Print(s_peer_desc_json);
|
||||
ESP_LOGI(TAG, "desc: %s", s_peer_desc);
|
||||
cJSON_Delete(s_peer_desc_json);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
juice_destroy(s_agent);
|
||||
s_agent = NULL;
|
||||
cJSON_Delete(s_peer_desc_json);
|
||||
s_peer_desc_json = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void local_handler(void *args, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
switch (id) {
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "local client connected");
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "local client disconnected");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGI(TAG, "local client error");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "local client event id:%d", (int)id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t create_local_client(void)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_mqtt_client_config_t mqtt_cfg = {
|
||||
.broker.address.transport = MQTT_TRANSPORT_OVER_TCP,
|
||||
.broker.address.hostname = wifi_get_ipv4(WIFI_IF_AP),
|
||||
.broker.address.port = CONFIG_EXAMPLE_MQTT_BROKER_PORT,
|
||||
.task.stack_size = CONFIG_EXAMPLE_MQTT_CLIENT_STACK_SIZE,
|
||||
.credentials.client_id = "local_mqtt"
|
||||
};
|
||||
s_local_mqtt = esp_mqtt_client_init(&mqtt_cfg);
|
||||
ESP_GOTO_ON_FALSE(s_local_mqtt, ESP_ERR_NO_MEM, err, TAG, "Failed to create mqtt client");
|
||||
ESP_GOTO_ON_ERROR(esp_mqtt_client_register_event(s_local_mqtt, ESP_EVENT_ANY_ID, local_handler, NULL),
|
||||
err, TAG, "Failed to register mqtt event handler");
|
||||
ESP_GOTO_ON_ERROR(esp_mqtt_client_start(s_local_mqtt), err, TAG, "Failed to start mqtt client");
|
||||
|
||||
return ESP_OK;
|
||||
err:
|
||||
esp_mqtt_client_destroy(s_local_mqtt);
|
||||
s_local_mqtt = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void handle_message(char *client, char *topic, char *payload, int len, int qos, int retain)
|
||||
{
|
||||
if (client && strcmp(client, "local_mqtt") == 0 ) {
|
||||
// This is our little local client -- do not forward
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "handle_message topic:%s", topic);
|
||||
ESP_LOGI(TAG, "handle_message data:%.*s", len, payload);
|
||||
ESP_LOGI(TAG, "handle_message qos=%d, retain=%d", qos, retain);
|
||||
if (s_local_mqtt && s_agent) {
|
||||
int topic_len = strlen(topic) + 1; // null term
|
||||
int topic_len_aligned = ALIGN(topic_len);
|
||||
int total_msg_len = 2 + 2 /* msg_wrap header */ + topic_len_aligned + len;
|
||||
if (total_msg_len > MAX_BUFFER_SIZE) {
|
||||
ESP_LOGE(TAG, "Fail to forward, message too long");
|
||||
return;
|
||||
}
|
||||
message_wrap_t *message = (message_wrap_t *)s_buffer;
|
||||
message->topic_len = topic_len;
|
||||
message->data_len = len;
|
||||
|
||||
memcpy(s_buffer + 4, topic, topic_len);
|
||||
memcpy(s_buffer + 4 + topic_len_aligned, payload, len);
|
||||
juice_send(s_agent, s_buffer, total_msg_len);
|
||||
}
|
||||
}
|
||||
|
||||
static void broker_task(void *ctx)
|
||||
{
|
||||
struct mosq_broker_config config = { .host = wifi_get_ipv4(WIFI_IF_AP), .port = CONFIG_EXAMPLE_MQTT_BROKER_PORT, .handle_message_cb = handle_message };
|
||||
mosq_broker_run(&config);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static esp_err_t create_local_broker(void)
|
||||
{
|
||||
return xTaskCreate(broker_task, "mqtt_broker_task", 1024 * 32, NULL, 5, NULL) == pdTRUE ?
|
||||
ESP_OK : ESP_FAIL;
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_mac.h"
|
||||
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
static const char *TAG = "serverless_wifi";
|
||||
static EventGroupHandle_t s_wifi_events;
|
||||
static int s_retry_num = 0;
|
||||
static const int s_max_retry = 30;
|
||||
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
|
||||
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *) event_data;
|
||||
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
|
||||
MAC2STR(event->mac), event->aid);
|
||||
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||
wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *) event_data;
|
||||
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
|
||||
MAC2STR(event->mac), event->aid);
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < s_max_retry) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGI(TAG, "retry to connect to the AP");
|
||||
} else {
|
||||
xEventGroupSetBits(s_wifi_events, WIFI_FAIL_BIT);
|
||||
}
|
||||
ESP_LOGI(TAG, "Connect to the AP fail");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
|
||||
ESP_LOGI(TAG, "Got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
s_retry_num = 0;
|
||||
xEventGroupSetBits(s_wifi_events, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t wifi_connect(void)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(s_wifi_events = xEventGroupCreate(), ESP_ERR_NO_MEM, err, TAG, "Failed to create wifi_events");
|
||||
ESP_GOTO_ON_ERROR(nvs_flash_init(), err, TAG, "Failed to init nvs flash");
|
||||
ESP_GOTO_ON_ERROR(esp_netif_init(), err, TAG, "Failed to init esp_netif");
|
||||
ESP_GOTO_ON_ERROR(esp_event_loop_create_default(), err, TAG, "Failed to create default event loop");
|
||||
ESP_GOTO_ON_ERROR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL),
|
||||
err, TAG, "Failed to register WiFi event handler");
|
||||
ESP_GOTO_ON_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL),
|
||||
err, TAG, "Failed to register IP event handler");
|
||||
|
||||
// Initialize WiFi
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_GOTO_ON_ERROR(esp_wifi_init(&cfg), err, TAG, "Failed to initialize WiFi");
|
||||
ESP_GOTO_ON_ERROR(esp_wifi_set_mode(WIFI_MODE_APSTA), err, TAG, "Failed to set STA+AP mode");
|
||||
|
||||
// Initialize AP
|
||||
esp_netif_t *ap = esp_netif_create_default_wifi_ap();
|
||||
ESP_GOTO_ON_FALSE(ap, ESP_FAIL, err, TAG, "Failed to create AP network interface");
|
||||
wifi_config_t wifi_ap_config = {
|
||||
.ap = {
|
||||
.ssid = CONFIG_EXAMPLE_AP_SSID,
|
||||
.password = CONFIG_EXAMPLE_AP_PASSWORD,
|
||||
.authmode = WIFI_AUTH_WPA2_PSK,
|
||||
.max_connection = 4,
|
||||
},
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config), err, TAG, "Failed to set AP config");
|
||||
|
||||
|
||||
// Initialize STA
|
||||
esp_netif_t *sta = esp_netif_create_default_wifi_sta();
|
||||
ESP_GOTO_ON_FALSE(sta, ESP_FAIL, err, TAG, "Failed to create WiFi station network interface");
|
||||
wifi_config_t wifi_sta_config = {
|
||||
.sta = {
|
||||
.ssid = CONFIG_EXAMPLE_STA_SSID,
|
||||
.password = CONFIG_EXAMPLE_STA_PASSWORD,
|
||||
},
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config), err, TAG, "Failed to set STA config");
|
||||
|
||||
// Start WiFi
|
||||
ESP_GOTO_ON_ERROR(esp_wifi_start(), err, TAG, "Failed to start WiFi");
|
||||
|
||||
// Wait for connection
|
||||
EventBits_t bits = xEventGroupWaitBits(s_wifi_events, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||
pdFALSE, pdFALSE, pdMS_TO_TICKS(30000));
|
||||
ESP_GOTO_ON_FALSE((bits & WIFI_CONNECTED_BIT) == WIFI_CONNECTED_BIT, ESP_FAIL, err,
|
||||
TAG, "Failed to obtain IP address from WiFi station");
|
||||
return ESP_OK;
|
||||
err:
|
||||
esp_wifi_stop();
|
||||
esp_wifi_deinit();
|
||||
nvs_flash_deinit();
|
||||
esp_netif_deinit();
|
||||
esp_event_loop_delete_default();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
_Thread_local char s_ipv4_addr[4 * 4]; // 4 octets + '.'/term
|
||||
|
||||
char *wifi_get_ipv4(wifi_interface_t interface)
|
||||
{
|
||||
esp_netif_t *netif = esp_netif_get_handle_from_ifkey(interface == WIFI_IF_AP ? "WIFI_AP_DEF" : "WIFI_STA_DEF");
|
||||
ESP_RETURN_ON_FALSE(netif, NULL, TAG, "Failed to find default Wi-Fi netif");
|
||||
esp_netif_ip_info_t ip_info;
|
||||
ESP_RETURN_ON_FALSE(esp_netif_get_ip_info(netif, &ip_info) == ESP_OK, NULL, TAG, "Failed to get IP from netif");
|
||||
ESP_RETURN_ON_FALSE(esp_ip4addr_ntoa(&ip_info.ip, s_ipv4_addr, sizeof(s_ipv4_addr)) != NULL, NULL, TAG, "Failed to convert IP");
|
||||
return s_ipv4_addr;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
|
||||
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=32768
|
BIN
components/mosquitto/examples/serverless_mqtt/serverless.png
Normal file
BIN
components/mosquitto/examples/serverless_mqtt/serverless.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
@ -1,5 +1,7 @@
|
||||
version: "2.0.28~0"
|
||||
version: "2.0.20~1"
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/mosquitto
|
||||
description: The component provides a simple ESP32 port of mosquitto broker
|
||||
dependencies:
|
||||
idf: '>=5.1'
|
||||
espressif/sock_utils:
|
||||
version: '^0.2'
|
||||
|
Submodule components/mosquitto/mosquitto updated: 3923526c6b...a196c2b244
@ -101,6 +101,8 @@ void mosq_broker_stop(void)
|
||||
run = 0;
|
||||
}
|
||||
|
||||
extern mosq_message_cb_t g_mosq_message_callback;
|
||||
|
||||
int mosq_broker_run(struct mosq_broker_config *broker_config)
|
||||
{
|
||||
|
||||
@ -125,6 +127,9 @@ int mosq_broker_run(struct mosq_broker_config *broker_config)
|
||||
if (broker_config->tls_cfg) {
|
||||
net__set_tls_config(broker_config->tls_cfg);
|
||||
}
|
||||
if (broker_config->handle_message_cb) {
|
||||
g_mosq_message_callback = broker_config->handle_message_cb;
|
||||
}
|
||||
|
||||
db.config = &config;
|
||||
|
||||
|
@ -13,7 +13,9 @@
|
||||
#include "util_mosq.h"
|
||||
#include "utlist.h"
|
||||
#include "lib_load.h"
|
||||
#include "mosq_broker.h"
|
||||
|
||||
mosq_message_cb_t g_mosq_message_callback = NULL;
|
||||
|
||||
int mosquitto_callback_register(
|
||||
mosquitto_plugin_id_t *identifier,
|
||||
@ -44,5 +46,8 @@ void plugin__handle_disconnect(struct mosquitto *context, int reason)
|
||||
|
||||
int plugin__handle_message(struct mosquitto *context, struct mosquitto_msg_store *stored)
|
||||
{
|
||||
if (g_mosq_message_callback) {
|
||||
g_mosq_message_callback(context->id, stored->topic, stored->payload, stored->payloadlen, stored->qos, stored->retain);
|
||||
}
|
||||
return MOSQ_ERR_SUCCESS;
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Roger Light <roger@atchoo.org>
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*
|
||||
* SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*/
|
||||
#include "ifaddrs.h"
|
||||
|
||||
// Dummy implementation of getifaddrs()
|
||||
// TODO: Implement this if we need to use bind_interface option of listener's config
|
||||
int getifaddrs(struct ifaddrs **ifap)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void freeifaddrs(struct ifaddrs *ifa)
|
||||
{
|
||||
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
|
||||
struct mosquitto__config;
|
||||
|
||||
typedef void (*mosq_message_cb_t)(char *client, char *topic, char *data, int len, int qos, int retain);
|
||||
/**
|
||||
* @brief Mosquitto configuration structure
|
||||
*
|
||||
@ -24,6 +25,10 @@ struct mosq_broker_config {
|
||||
* You can open the respective docs with this idf.py command:
|
||||
* `idf.py docs -sp api-reference/protocols/esp_tls.html`
|
||||
*/
|
||||
void (*handle_message_cb)(char *client, char *topic, char *data, int len, int qos, int retain); /*!<
|
||||
* On message callback. If configured, user function is called
|
||||
* whenever mosquitto processes a message.
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -5,20 +5,11 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include <ctype.h>
|
||||
#include "net/if.h"
|
||||
|
||||
#undef isspace
|
||||
#define isspace(__c) (__ctype_lookup((int)__c)&_S)
|
||||
|
||||
#include_next "config.h"
|
||||
#define VERSION "v2.0.18~0"
|
||||
|
||||
#define _SC_OPEN_MAX_OVERRIDE 4
|
||||
|
||||
// used to override syscall for obtaining max open fds
|
||||
static inline long sysconf(int arg)
|
||||
{
|
||||
if (arg == _SC_OPEN_MAX_OVERRIDE) {
|
||||
return 64;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#define VERSION "v2.0.20~1"
|
||||
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define gai_strerror(x) "gai_strerror() not supported"
|
||||
|
||||
struct ifaddrs {
|
||||
struct ifaddrs *ifa_next; /* Next item in list */
|
||||
char *ifa_name; /* Name of interface */
|
||||
struct sockaddr *ifa_addr; /* Address of interface */
|
||||
};
|
||||
|
||||
int getifaddrs(struct ifaddrs **ifap);
|
||||
void freeifaddrs(struct ifaddrs *ifa);
|
@ -1,8 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include_next "sys/poll.h"
|
18
components/mosquitto/port/sysconf.c
Normal file
18
components/mosquitto/port/sysconf.c
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define _SC_OPEN_MAX_OVERRIDE 4
|
||||
|
||||
// used to override syscall for obtaining max open fds
|
||||
long sysconf(int arg)
|
||||
{
|
||||
if (arg == _SC_OPEN_MAX_OVERRIDE) {
|
||||
return 64;
|
||||
}
|
||||
return -1;
|
||||
}
|
5
components/mosquitto/test/README.md
Normal file
5
components/mosquitto/test/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# ESP32 mosquitto tests
|
||||
|
||||
Mosquitto component doesn't have any tests yet, but we upcycle IDF mqtt tests and run them with the current version of mosquitto.
|
||||
For that we need to update the IDF test project's `idf_component.yml` file to reference this actual version of mosquitto.
|
||||
We also need to update some pytest decorators to run only relevant test cases. See the [replacement](./replace_decorators.py) script.
|
42
components/mosquitto/test/replace_decorators.py
Normal file
42
components/mosquitto/test/replace_decorators.py
Normal file
@ -0,0 +1,42 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
|
||||
# This script replaces the `@pytest` decorators in the test files
|
||||
# based on the value of the `config` parameter in the `@pytest` decorator.
|
||||
# to reuse mqtt test cases for mosquitto broker.
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def replace_decorators(in_file: str, out_file: str) -> None:
|
||||
with open(in_file, 'r') as file:
|
||||
content = file.read()
|
||||
|
||||
# we replace config decorators to differentiate between local mosquitto based tests
|
||||
pattern = r"@pytest\.mark\.parametrize\(\s*'config'\s*,\s*\[\s*'(.*?)'\s*\]\s*,.*\)"
|
||||
|
||||
def replacement(match):
|
||||
config_value = match.group(1)
|
||||
if config_value == 'local_broker':
|
||||
return '@pytest.mark.test_mosquitto'
|
||||
else:
|
||||
return '@pytest.mark.test_mqtt'
|
||||
|
||||
# Replace occurrences
|
||||
updated_content = re.sub(pattern, replacement, content)
|
||||
|
||||
with open(out_file, 'w') as file:
|
||||
file.write(updated_content)
|
||||
|
||||
|
||||
# Main function to handle arguments
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print('Usage: python replace_decorators.py <in_file> <out_file>')
|
||||
sys.exit(1)
|
||||
|
||||
in_file = sys.argv[1]
|
||||
out_file = sys.argv[2]
|
||||
replace_decorators(in_file, out_file)
|
||||
print(f'Replacements completed')
|
@ -3,6 +3,6 @@ commitizen:
|
||||
bump_message: 'bump(sockutls): $current_version -> $new_version'
|
||||
pre_bump_hooks: python ../../ci/changelog.py sock_utils
|
||||
tag_format: sock_utils-v$version
|
||||
version: 0.1.0
|
||||
version: 0.2.2
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## [0.2.2](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.2.2)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix gai_strerror() impl to return const string ([f12a2056](https://github.com/espressif/esp-protocols/commit/f12a2056))
|
||||
|
||||
## [0.2.1](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.2.1)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix potential macro colission including standard headers ([ade9448c](https://github.com/espressif/esp-protocols/commit/ade9448c))
|
||||
|
||||
## [0.2.0](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.2.0)
|
||||
|
||||
### Features
|
||||
|
||||
- Declare socketpair and gai_strerror via standard headers ([b090a3cb](https://github.com/espressif/esp-protocols/commit/b090a3cb))
|
||||
- Add support for gethostname() ([f7c0b756](https://github.com/espressif/esp-protocols/commit/f7c0b756))
|
||||
|
||||
## [0.1.0](https://github.com/espressif/esp-protocols/commits/sock_utils-v0.1.0)
|
||||
|
||||
### Features
|
||||
|
@ -2,5 +2,15 @@ idf_component_register(SRCS "src/getnameinfo.c"
|
||||
"src/ifaddrs.c"
|
||||
"src/gai_strerror.c"
|
||||
"src/socketpair.c"
|
||||
"src/gethostname.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES lwip esp_netif)
|
||||
|
||||
# To support declarations from standard headers in lwip component
|
||||
# - socket pair from lwip/sockets.h
|
||||
# - gai_strerror from lwip/netdb.h
|
||||
# also need to make lwip depend on the sock_utils lib
|
||||
idf_component_get_property(lwip lwip COMPONENT_LIB)
|
||||
target_compile_definitions(${lwip} PUBLIC LWIP_SOCKET_HAS_SOCKETPAIR=1)
|
||||
target_compile_definitions(${lwip} PUBLIC LWIP_NETDB_HAS_GAI_STRERROR=1)
|
||||
target_link_libraries(${lwip} PUBLIC ${COMPONENT_LIB})
|
||||
|
@ -6,12 +6,17 @@ This component provides simplified implementations of common socket-related util
|
||||
## Supported Functions
|
||||
|
||||
|
||||
| API | Description | Limitations |
|
||||
|------------------|-------------------------------------------------------------|-------------------------------------------------------------------|
|
||||
| `ifaddrs()` | Retrieves interface addresses using `esp_netif` | IPv4 addresses only |
|
||||
| `socketpair()` | Creates a pair of connected sockets using `lwIP` loopback stream sockets | IPv4 sockets only |
|
||||
| `pipe()` | Wraps `socketpair()` to provide unidirectional pipe-like functionality | Uses bidirectional sockets in place of true pipes |
|
||||
| `getnameinfo()` | Converts IP addresses to human-readable form using `lwIP`'s `inet_ntop()` | IPv4 only; supports `NI_NUMERICHOST` and `NI_NUMERICSERV` flags only |
|
||||
| `gai_strerror()` | Returns error code as a string | Simple numeric string representation only |
|
||||
| API | Description | Limitations | Declared in |
|
||||
|--------------------|-------------------------------------------------------------|-------------------------------------------------------------------|----------------------------------------|
|
||||
| `ifaddrs()` | Retrieves interface addresses using `esp_netif` | IPv4 addresses only | `ifaddrs.h` |
|
||||
| `socketpair()` *) | Creates a pair of connected sockets using `lwIP` loopback stream sockets | IPv4 sockets only | `socketpair.h`, `sys/socket.h` **) |
|
||||
| `pipe()` *) | Wraps `socketpair()` to provide unidirectional pipe-like functionality | Uses bidirectional sockets in place of true pipes | `socketpair.h`, `unistd.h` ***) |
|
||||
| `getnameinfo()` | Converts IP addresses to human-readable form using `lwIP`'s `inet_ntop()` | IPv4 only; supports `NI_NUMERICHOST` and `NI_NUMERICSERV` flags only | `getnameinfo.h`, `netdb.h` in ESP-IDF |
|
||||
| `gai_strerror()` | Returns error code as a string | Simple numeric string representation only | `gai_strerror.h`, `netdb.h` **) |
|
||||
| `gethostname()` | Returns lwip netif hostname | Not a system-wide hostname, but interface specific hostname | `gethostname.h`, `unistd.h` in ESP-IDF |
|
||||
|
||||
**Note**: `socketpair()` and `pipe()` are built on top of `lwIP` TCP sockets, inheriting the same characteristics. For instance, the maximum transmit buffer size is based on the `TCP_SND_BUF` setting.
|
||||
**Notes**:
|
||||
|
||||
- **`*)`** `socketpair()` and `pipe()` are built on top of `lwIP` TCP sockets, inheriting the same characteristics. For instance, the maximum transmit buffer size is based on the `TCP_SND_BUF` setting.
|
||||
- **`**)`** `socketpair()` and `gai_strerror()` are declared in sock_utils header files, the declaration is propagated to ESP-IDF from v5.5 to the official header files. If you're using older IDF version, you need to manually pre-include related header files from the sock_utils public include directory.
|
||||
- **`***)`** `pipe()` is declared in compiler's `sys/unistd.h`.
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: 0.1.0
|
||||
version: 0.2.2
|
||||
description: The component provides helper implementation of common system/socket utilities
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/sock_utils
|
||||
dependencies:
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -18,13 +18,14 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Returns a numeric string representing of `getaddrinfo()` error code.
|
||||
* @brief Returns a string representing of `getaddrinfo()` error code.
|
||||
*
|
||||
* @param[in] ecode Error code returned by `getaddrinfo()`.
|
||||
* @param[in] errcode Error code returned by `getaddrinfo()`.
|
||||
*
|
||||
* @return A pointer to a string describing the error.
|
||||
* @return A pointer to a string containing the error code, for example "EAI_NONAME"
|
||||
* for EAI_NONAME error type.
|
||||
*/
|
||||
const char *gai_strerror(int ecode);
|
||||
const char *gai_strerror(int errcode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
51
components/sock_utils/include/gethostname.h
Normal file
51
components/sock_utils/include/gethostname.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <unistd.h>
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_LINUX
|
||||
// namespace with esp_ on linux to avoid conflict of symbols
|
||||
#define gethostname esp_gethostname
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Retrieves the hostname of the device.
|
||||
*
|
||||
* This function provides the hostname associated with the network interface.
|
||||
* Unlike the standard behavior where the hostname represents a system-wide name,
|
||||
* this implementation returns lwip netif hostname (used as a hostname in DHCP packets)
|
||||
*
|
||||
* @param[out] name A pointer to a buffer where the hostname will be stored.
|
||||
* The buffer must be allocated by the caller.
|
||||
* @param[in] len The size of the buffer pointed to by @p name. The hostname,
|
||||
* including the null-terminator, must fit within this size.
|
||||
*
|
||||
* @return
|
||||
* - 0 on success
|
||||
* - -1 on error, with `errno` set to indicate the error:
|
||||
* - `EINVAL`: Invalid argument, name is NULL, or hostname is too long
|
||||
*
|
||||
* @note This implementation retrieves the hostname associated with the network
|
||||
* interface using the `esp_netif_get_hostname()` function, which in turn
|
||||
* returns lwip netif hostname used in DHCP packets if LWIP_NETIF_HOSTNAME=1 (hardcoded)
|
||||
* in ESP-IDF lwip port.
|
||||
* As there could be multiple network interfaces in the system, the logic tries
|
||||
* to find the default (active) netif first, then it looks for any (inactive) netif
|
||||
* with highest route priority. If none of the above found or esp_netif_get_hostname() fails
|
||||
* for the selected interface, this API returns the default value of `CONFIG_LWIP_LOCAL_HOSTNAME`,
|
||||
* the local hostname from lwip component configuration menu.
|
||||
*/
|
||||
int gethostname(char *name, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -5,6 +5,17 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#ifndef CONFIG_IDF_TARGET_LINUX
|
||||
#include <netinet/in.h> // For network-related definitions
|
||||
#include <sys/socket.h> // For socket-related definitions
|
||||
#include <net/if.h> // For interface flags (e.g., IFF_UP)
|
||||
#include <netdb.h> // For NI_NUMERICHOST, NI_NUMERICSERV, etc.
|
||||
#include <errno.h> // For EAI_BADFLAGS
|
||||
#include <sys/un.h> // For AF_UNIX
|
||||
#include <sys/types.h> // For PF_LOCAL
|
||||
#endif
|
||||
|
||||
#ifndef NI_NUMERICHOST
|
||||
#define NI_NUMERICHOST 0x1
|
||||
#endif
|
||||
@ -32,3 +43,10 @@
|
||||
#ifndef AF_UNIX
|
||||
#define AF_UNIX 1
|
||||
#endif
|
||||
|
||||
#ifndef PF_LOCAL
|
||||
/*
|
||||
* In POSIX, AF_UNIX and PF_LOCAL are essentially synonymous.
|
||||
*/
|
||||
#define PF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
@ -1,17 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "gai_strerror.h"
|
||||
#include "lwip/netdb.h"
|
||||
|
||||
_Thread_local char gai_strerror_string[32];
|
||||
#define HANDLE_GAI_ERROR(code) \
|
||||
case code: return #code;
|
||||
|
||||
const char *gai_strerror(int ecode)
|
||||
const char *gai_strerror(int errcode)
|
||||
{
|
||||
if (snprintf(gai_strerror_string, sizeof(gai_strerror_string), "EAI error:%d", ecode) < 0) {
|
||||
return "gai_strerror() failed";
|
||||
switch (errcode) {
|
||||
/* lwip defined DNS codes */
|
||||
HANDLE_GAI_ERROR(EAI_BADFLAGS)
|
||||
HANDLE_GAI_ERROR(EAI_FAIL)
|
||||
HANDLE_GAI_ERROR(EAI_FAMILY)
|
||||
HANDLE_GAI_ERROR(EAI_MEMORY)
|
||||
HANDLE_GAI_ERROR(EAI_NONAME)
|
||||
HANDLE_GAI_ERROR(EAI_SERVICE)
|
||||
/* other error codes optionally defined in platform/newlib or toolchain */
|
||||
#ifdef EAI_AGAIN
|
||||
HANDLE_GAI_ERROR(EAI_AGAIN)
|
||||
#endif
|
||||
#ifdef EAI_SOCKTYPE
|
||||
HANDLE_GAI_ERROR(EAI_SOCKTYPE)
|
||||
#endif
|
||||
#ifdef EAI_SYSTEM
|
||||
HANDLE_GAI_ERROR(EAI_SYSTEM)
|
||||
#endif
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
return gai_strerror_string;
|
||||
}
|
||||
|
46
components/sock_utils/src/gethostname.c
Normal file
46
components/sock_utils/src/gethostname.c
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "gethostname.h"
|
||||
#include "esp_netif.h"
|
||||
#include "errno.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
static bool highest_prio_netif(esp_netif_t *netif, void *ctx)
|
||||
{
|
||||
esp_netif_t **highest_so_far = ctx;
|
||||
if (esp_netif_get_route_prio(netif) > esp_netif_get_route_prio(*highest_so_far)) {
|
||||
*highest_so_far = netif;
|
||||
}
|
||||
return false; // go over the entire list to find the netif with the highest route-prio
|
||||
}
|
||||
|
||||
int gethostname(char *name, size_t len)
|
||||
{
|
||||
if (name == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
const char *netif_hostname = CONFIG_LWIP_LOCAL_HOSTNAME; // default value from Kconfig
|
||||
|
||||
// Find the default netif
|
||||
esp_netif_t *default_netif = esp_netif_get_default_netif();
|
||||
if (default_netif == NULL) { // if no netif is active/up -> find the highest prio netif
|
||||
esp_netif_find_if(highest_prio_netif, &default_netif);
|
||||
}
|
||||
// now the `default_netif` could be NULL and/or the esp_netif_get_hostname() could fail
|
||||
// but we ignore the return code, as if it fails, the `netif_hostname` still holds the default value
|
||||
esp_netif_get_hostname(default_netif, &netif_hostname);
|
||||
|
||||
size_t hostname_len;
|
||||
if (netif_hostname == NULL || len < (hostname_len = strlen(netif_hostname) + 1)) { // including the NULL terminator
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memcpy(name, netif_hostname, hostname_len);
|
||||
return 0;
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "gethostname.h"
|
||||
#include "ifaddrs.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_event.h"
|
||||
@ -148,6 +149,30 @@ TEST_CASE("gai_strerror()", "[sock_utils]")
|
||||
CHECK(str_error != NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("gethostname()", "[sock_utils]")
|
||||
{
|
||||
const char *test_netif_name = "station";
|
||||
char hostname[32];
|
||||
int ret;
|
||||
|
||||
// expect failure
|
||||
ret = gethostname(hostname, strlen(CONFIG_LWIP_LOCAL_HOSTNAME) - 1);
|
||||
CHECK(ret == -1);
|
||||
|
||||
// happy flow with the default name
|
||||
ret = gethostname(hostname, sizeof(hostname));
|
||||
CHECK(ret == 0);
|
||||
CHECK(strcmp(hostname, CONFIG_LWIP_LOCAL_HOSTNAME) == 0);
|
||||
|
||||
// happy flow with the netif name
|
||||
esp_netif_t *esp_netif = create_test_netif(test_netif_name, 1);
|
||||
REQUIRE(esp_netif != NULL);
|
||||
CHECK(esp_netif_set_hostname(esp_netif, test_netif_name) == ESP_OK);
|
||||
ret = gethostname(hostname, sizeof(hostname));
|
||||
CHECK(ret == 0);
|
||||
CHECK(strcmp(hostname, test_netif_name) == 0);
|
||||
esp_netif_destroy(esp_netif);
|
||||
}
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
|
@ -66,13 +66,43 @@ Configuration:
|
||||
.. note:: If you want to verify the server, then you need to provide a certificate in PEM format, and provide to ``cert_pem`` in :cpp:type:`websocket_client_config_t`. If no certficate is provided then the TLS connection will default to not requiring verification.
|
||||
|
||||
PEM certificate for this example could be extracted from an openssl `s_client` command connecting to websocket.org.
|
||||
In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used).
|
||||
```
|
||||
echo "" | openssl s_client -showcerts -connect websocket.org:443 | sed -n "1,/Root/d; /BEGIN/,/END/p" | openssl x509 -outform PEM >websocket_org.pem
|
||||
```
|
||||
In case a host operating system has `openssl` and `sed` packages installed, one could execute the following command to download and save the root or intermediate root certificate to a file (Note for Windows users: Both Linux like environment or Windows native packages may be used). ::
|
||||
|
||||
echo "" | openssl s_client -showcerts -connect websocket.org:443 \
|
||||
| sed -n "1,/Root/d; /BEGIN/,/END/p" \
|
||||
| openssl x509 -outform PEM \
|
||||
> websocket_org.pem
|
||||
|
||||
This command will extract the second certificate in the chain and save it as a pem-file.
|
||||
|
||||
Mutual TLS with DS Peripheral
|
||||
"""""""""""""""""""""""""""""
|
||||
|
||||
To leverage the Digital Signature (DS) peripheral on supported targets, use `esp_secure_cert_mgr <https://github.com/espressif/esp_secure_cert_mgr/>`_ to flash an encrypted client certificate. In your project, add the dependency: ::
|
||||
|
||||
idf.py add-dependency esp_secure_cert_mgr
|
||||
|
||||
Set ``client_cert`` and ``client_ds_data`` in the config struct:
|
||||
|
||||
.. code:: c
|
||||
|
||||
char *client_cert = NULL;
|
||||
uint32_t client_cert_len = 0;
|
||||
esp_err_t err = esp_secure_cert_get_device_cert(&client_cert, &client_cert_len);
|
||||
assert(err == ESP_OK);
|
||||
|
||||
esp_ds_data_ctx_t *ds_data = esp_secure_cert_get_ds_ctx();
|
||||
assert(ds_data != NULL);
|
||||
|
||||
esp_websocket_client_config_t config = {
|
||||
.uri = "wss://echo.websocket.org",
|
||||
.cert_pem = (const char *)websocket_org_pem_start,
|
||||
.client_cert = client_cert,
|
||||
.client_ds_data = ds_data,
|
||||
};
|
||||
|
||||
.. note:: ``client_cert`` provided by `esp_secure_cert_mgr` is a null-terminated PEM; so ``client_cert_len`` (DER format) should not be set.
|
||||
|
||||
Subprotocol
|
||||
^^^^^^^^^^^
|
||||
|
||||
@ -91,14 +121,14 @@ For more options on :cpp:type:`esp_websocket_client_config_t`, please refer to A
|
||||
|
||||
Events
|
||||
------
|
||||
* `WEBSOCKET_EVENT_BEGIN': The client thread is running.
|
||||
* `WEBSOCKET_EVENT_BEGIN`: The client thread is running.
|
||||
* `WEBSOCKET_EVENT_BEFORE_CONNECT`: The client is about to connect.
|
||||
* `WEBSOCKET_EVENT_CONNECTED`: The client has successfully established a connection to the server. The client is now ready to send and receive data. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_DATA`: The client has successfully received and parsed a WebSocket frame. The event data contains a pointer to the payload data, the length of the payload data as well as the opcode of the received frame. A message may be fragmented into multiple events if the length exceeds the buffer size. This event will also be posted for non-payload frames, e.g. pong or connection close frames.
|
||||
* `WEBSOCKET_EVENT_ERROR`: The client has experienced an error. Examples include transport write or read failures.
|
||||
* `WEBSOCKET_EVENT_DISCONNECTED`: The client has aborted the connection due to the transport layer failing to read data, e.g. because the server is unavailable. Contains no event data.
|
||||
* `WEBSOCKET_EVENT_CLOSED`: The connection has been closed cleanly.
|
||||
* `WEBSOCKET_EVENT_FINISH': The client thread is about to exit.
|
||||
* `WEBSOCKET_EVENT_FINISH`: The client thread is about to exit.
|
||||
|
||||
If the client handle is needed in the event handler it can be accessed through the pointer passed to the event handler:
|
||||
|
||||
|
@ -1,8 +1 @@
|
||||
sphinxcontrib-applehelp==1.0.4
|
||||
sphinxcontrib_devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==2.0.1
|
||||
sphinxcontrib-serializinghtml==1.1.5
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
breathe==4.35
|
||||
recommonmark==0.7.1
|
||||
esp-docs==1.7.1
|
||||
esp-docs>=2.0.0
|
||||
|
Reference in New Issue
Block a user