mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-25 08:17:28 +02:00
Compare commits
58 Commits
modem-v1.3
...
mdns-v1.5.
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
f7c0b7564a |
112
.github/workflows/mosq__build.yml
vendored
112
.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,105 @@ 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:
|
||||
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
|
||||
|
@ -0,0 +1 @@
|
||||
components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c
|
||||
|
@ -122,12 +122,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;
|
||||
|
@ -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.1
|
||||
version_files:
|
||||
- idf_component.yml
|
||||
|
@ -1,5 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
## [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.1"
|
||||
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)
|
||||
@ -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;
|
||||
}
|
||||
@ -2354,6 +2355,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 +2389,53 @@ 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);
|
||||
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 +2607,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 +2851,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 +2960,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 +3549,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 +3851,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 +4298,7 @@ void mdns_parse_packet(mdns_rx_packet_t *packet)
|
||||
} else {
|
||||
free(out_sync_browse);
|
||||
}
|
||||
out_sync_browse = NULL;
|
||||
}
|
||||
|
||||
clear_rx_packet:
|
||||
@ -4269,15 +4335,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 +4538,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 +5405,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 +5955,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 +5964,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 +6411,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 +6499,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 +6582,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)
|
||||
{
|
||||
|
@ -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