From 3f4a2f609368b7bbc821d60c30f4c5d19c66f193 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Mon, 18 Nov 2024 07:19:24 +0100 Subject: [PATCH 1/5] fix(mdns): Rust experimenatation --- components/mdns/CMakeLists.txt | 2 +- components/mdns/mdns.c | 9 +++++++-- components/mdns/mdns_networking_socket.c | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt index e0b2a4d9e0..915b9bd00e 100644 --- a/components/mdns/CMakeLists.txt +++ b/components/mdns/CMakeLists.txt @@ -14,7 +14,7 @@ idf_build_get_property(target IDF_TARGET) if(${target} STREQUAL "linux") set(dependencies esp_netif_linux esp_event) set(private_dependencies esp_timer console esp_system) - set(srcs "mdns.c" ${MDNS_NETWORKING} ${MDNS_CONSOLE}) + set(srcs ${MDNS_NETWORKING} ${MDNS_CONSOLE}) else() set(dependencies lwip console esp_netif) set(private_dependencies esp_timer esp_wifi) diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index 113a1e999b..ca9d87ddbe 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -176,7 +176,7 @@ static inline esp_netif_t *esp_netif_from_preset_if(mdns_predef_if_t predef_if) * @param tcpip_if Ordinal number of the interface * @return Pointer ot the esp_netif object if the interface is available, NULL otherwise */ -esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if) +esp_netif_t *_mdns_get_esp_netif23(mdns_if_t tcpip_if) { if (tcpip_if < MDNS_MAX_INTERFACES) { if (s_esp_netifs[tcpip_if].netif == NULL && s_esp_netifs[tcpip_if].predefined) { @@ -347,7 +347,7 @@ static bool _mdns_can_add_more_services(void) return true; } -esp_err_t _mdns_send_rx_action(mdns_rx_packet_t *packet) +esp_err_t _mdns_send_rx_action23(mdns_rx_packet_t *packet) { mdns_action_t *action = NULL; @@ -1544,6 +1544,11 @@ static void _mdns_dispatch_tx_packet(mdns_tx_packet_t *p) mdns_debug_packet(packet, index); #endif + ESP_LOG_BUFFER_HEXDUMP(TAG, packet, index, ESP_LOG_INFO); + for (int i = 0; i < index; ++i) { + printf("0x%02x, ", packet[i]); + } + printf("\n"); _mdns_udp_pcb_write(p->tcpip_if, p->ip_protocol, &p->dst, p->port, packet, index); } diff --git a/components/mdns/mdns_networking_socket.c b/components/mdns/mdns_networking_socket.c index a99a9cc2d9..d359a353cf 100644 --- a/components/mdns/mdns_networking_socket.c +++ b/components/mdns/mdns_networking_socket.c @@ -206,7 +206,7 @@ size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, c ESP_LOGE(TAG, "espaddr_to_inet() failed: Mismatch of IP protocols"); return 0; } - ESP_LOGD(TAG, "[sock=%d]: Sending to IP %s port %d", sock, get_string_address(&in_addr), port); + ESP_LOGI(TAG, "[sock=%d]: Sending to IP %s port %d", sock, get_string_address(&in_addr), port); ssize_t actual_len = sendto(sock, data, len, 0, (struct sockaddr *)&in_addr, ss_size); if (actual_len < 0) { ESP_LOGE(TAG, "[sock=%d]: _mdns_udp_pcb_write sendto() has failed\n errno=%d: %s", sock, errno, strerror(errno)); From b983fe48eb2064fe6f4101c1134b74b0cbb72112 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 22 Nov 2024 16:47:06 +0100 Subject: [PATCH 2/5] feat(mdns): WIP support for rust API --- .github/workflows/mdns__build-target-test.yml | 84 ------ .github/workflows/mdns__host-tests.yml | 64 ----- .github/workflows/mdns__rust.yml | 34 +++ components/mdns/CMakeLists.txt | 2 +- components/mdns/Cargo.toml | 13 + components/mdns/build.rs | 104 +++++++ .../mdns/examples/simple_query/CMakeLists.txt | 16 ++ .../examples/simple_query/main/CMakeLists.txt | 4 + .../simple_query/main/Kconfig.projbuild | 21 ++ .../simple_query/main/esp_system_linux2.c | 46 ++++ .../simple_query/main/idf_component.yml | 7 + .../mdns/examples/simple_query/main/main.c | 101 +++++++ .../examples/simple_query/sdkconfig.defaults | 7 + components/mdns/examples/usage.rs | 58 ++++ components/mdns/mdns_stub.c | 76 ++++++ components/mdns/src/lib.rs | 258 ++++++++++++++++++ 16 files changed, 746 insertions(+), 149 deletions(-) delete mode 100644 .github/workflows/mdns__build-target-test.yml delete mode 100644 .github/workflows/mdns__host-tests.yml create mode 100644 .github/workflows/mdns__rust.yml create mode 100644 components/mdns/Cargo.toml create mode 100644 components/mdns/build.rs create mode 100644 components/mdns/examples/simple_query/CMakeLists.txt create mode 100644 components/mdns/examples/simple_query/main/CMakeLists.txt create mode 100644 components/mdns/examples/simple_query/main/Kconfig.projbuild create mode 100644 components/mdns/examples/simple_query/main/esp_system_linux2.c create mode 100644 components/mdns/examples/simple_query/main/idf_component.yml create mode 100644 components/mdns/examples/simple_query/main/main.c create mode 100644 components/mdns/examples/simple_query/sdkconfig.defaults create mode 100644 components/mdns/examples/usage.rs create mode 100644 components/mdns/mdns_stub.c create mode 100644 components/mdns/src/lib.rs diff --git a/.github/workflows/mdns__build-target-test.yml b/.github/workflows/mdns__build-target-test.yml deleted file mode 100644 index d222307c79..0000000000 --- a/.github/workflows/mdns__build-target-test.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: "mdns: build/target-tests" - -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened, labeled] - -jobs: - build_mdns: - if: contains(github.event.pull_request.labels.*.name, 'mdns') || github.event_name == 'push' - name: Build - strategy: - matrix: - idf_ver: ["latest", "release-v5.0", "release-v5.2", "release-v5.3"] - test: [ { app: example, path: "examples/query_advertise" }, { app: unit_test, path: "tests/unit_test" }, { app: test_app, path: "tests/test_apps" } ] - runs-on: ubuntu-22.04 - container: espressif/idf:${{ matrix.idf_ver }} - steps: - - name: Checkout esp-protocols - uses: actions/checkout@v4 - - name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }} - shell: bash - run: | - . ${IDF_PATH}/export.sh - python -m pip install idf-build-apps - # Build default configs for all targets - python ./ci/build_apps.py components/mdns/${{ matrix.test.path }} -r default -d - # Build specific configs for test targets - python ./ci/build_apps.py components/mdns/${{ matrix.test.path }} - cd components/mdns/${{ matrix.test.path }} - for dir in `ls -d build_esp32_*`; do - $GITHUB_WORKSPACE/ci/clean_build_artifacts.sh `pwd`/$dir - zip -qur artifacts.zip $dir - done - - uses: actions/upload-artifact@v4 - with: - name: mdns_bin_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }} - path: components/mdns/${{ matrix.test.path }}/artifacts.zip - if-no-files-found: error - - target_tests_mdns: - # 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, 'mdns') || github.event_name == 'push' ) - name: Target Example and Unit tests - strategy: - matrix: - idf_ver: ["latest"] - idf_target: ["esp32"] - test: [ { app: example, path: "examples/query_advertise" }, { app: unit_test, path: "tests/unit_test" }, { app: test_app, path: "tests/test_apps" } ] - needs: build_mdns - runs-on: - - self-hosted - - ESP32-ETHERNET-KIT - steps: - - name: Clear repository - run: sudo rm -fr $GITHUB_WORKSPACE && mkdir $GITHUB_WORKSPACE - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 - with: - name: mdns_bin_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${{ matrix.test.app }} - path: components/mdns/${{ matrix.test.path }}/ci/ - - name: Install Python packages - env: - PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple" - run: | - sudo apt-get install -y dnsutils - - name: Run ${{ matrix.test.app }} application on ${{ matrix.idf_target }} - working-directory: components/mdns/${{ matrix.test.path }} - run: | - unzip ci/artifacts.zip -d ci - for dir in `ls -d ci/build_*`; do - rm -rf build sdkconfig.defaults - mv $dir build - python -m pytest --log-cli-level DEBUG --junit-xml=./results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}_${dir#"ci/build_"}.xml --target=${{ matrix.idf_target }} - done - - uses: actions/upload-artifact@v4 - if: always() - with: - name: results_${{ matrix.test.app }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml - path: components/mdns/${{ matrix.test.path }}/*.xml diff --git a/.github/workflows/mdns__host-tests.yml b/.github/workflows/mdns__host-tests.yml deleted file mode 100644 index 5ef6c8adee..0000000000 --- a/.github/workflows/mdns__host-tests.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: "mdns: host-tests" - -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize, reopened, labeled] - -jobs: - host_test_mdns: - if: contains(github.event.pull_request.labels.*.name, 'mdns') || github.event_name == 'push' - name: Host test build - runs-on: ubuntu-22.04 - container: espressif/idf:release-v5.3 - - steps: - - name: Checkout esp-protocols - uses: actions/checkout@v4 - with: - path: protocols - - - name: Build and Test - shell: bash - run: | - . ${IDF_PATH}/export.sh - python -m pip install idf-build-apps dnspython pytest pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf - cd $GITHUB_WORKSPACE/protocols - # Build host tests app (with all configs and targets supported) - python ./ci/build_apps.py components/mdns/tests/host_test/ - cd components/mdns/tests/host_test - # First run the linux_app and send a quick A query and a reverse query - ./build_linux_app/mdns_host.elf & - python dnsfixture.py A myesp.local --ip_only | xargs python dnsfixture.py X - # Next we run the pytest (using the console app) - pytest - - build_afl_host_test_mdns: - if: contains(github.event.pull_request.labels.*.name, 'mdns') || github.event_name == 'push' - name: Build AFL host test - strategy: - matrix: - idf_ver: ["latest"] - idf_target: ["esp32"] - - runs-on: ubuntu-22.04 - container: espressif/idf:${{ matrix.idf_ver }} - steps: - - name: Checkout esp-protocols - uses: actions/checkout@v4 - with: - path: esp-protocols - - name: Install Necessary Libs - run: | - apt-get update -y - apt-get install -y libbsd-dev - - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }} - env: - IDF_TARGET: ${{ matrix.idf_target }} - shell: bash - run: | - . ${IDF_PATH}/export.sh - cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_afl_fuzz_host/ - make INSTR=off diff --git a/.github/workflows/mdns__rust.yml b/.github/workflows/mdns__rust.yml new file mode 100644 index 0000000000..a32d0ab88c --- /dev/null +++ b/.github/workflows/mdns__rust.yml @@ -0,0 +1,34 @@ +name: "mdns: rust-tests" + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, labeled] + +jobs: + host_test_mdns: + if: contains(github.event.pull_request.labels.*.name, 'mdns') || github.event_name == 'push' + name: Host test build + runs-on: ubuntu-22.04 + container: espressif/idf:latest + + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v4 + + - name: Build and Test + shell: bash + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + # Add Rust to the current PATH + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + . "$HOME/.cargo/env" + rustc --version + cargo --version + . ${IDF_PATH}/export.sh + cd components/mdns/examples/simple_query/ + idf.py build + cd ../.. + COMPILE_COMMANDS_DIR=examples/simple_query/build/ cargo run --example usage diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt index 915b9bd00e..3a3b4c054b 100644 --- a/components/mdns/CMakeLists.txt +++ b/components/mdns/CMakeLists.txt @@ -14,7 +14,7 @@ idf_build_get_property(target IDF_TARGET) if(${target} STREQUAL "linux") set(dependencies esp_netif_linux esp_event) set(private_dependencies esp_timer console esp_system) - set(srcs ${MDNS_NETWORKING} ${MDNS_CONSOLE}) + set(srcs "mdns_stub.c" ${MDNS_NETWORKING} ${MDNS_CONSOLE}) else() set(dependencies lwip console esp_netif) set(private_dependencies esp_timer esp_wifi) diff --git a/components/mdns/Cargo.toml b/components/mdns/Cargo.toml new file mode 100644 index 0000000000..09a10e7458 --- /dev/null +++ b/components/mdns/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mdns" +version = "0.1.0" +edition = "2021" + +[dependencies] +libc = "0.2" +dns-parser = "0.8" + +[build-dependencies] +cc = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/components/mdns/build.rs b/components/mdns/build.rs new file mode 100644 index 0000000000..8647aa0c02 --- /dev/null +++ b/components/mdns/build.rs @@ -0,0 +1,104 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct CompileCommand { + directory: String, + command: String, + file: String, +} + +fn main() { + // Get the directory for compile_commands.json from an environment variable + let compile_commands_dir = env::var("COMPILE_COMMANDS_DIR") + .unwrap_or_else(|_| ".".to_string()); // Default to current directory + + // Construct the path to the compile_commands.json file + let compile_commands_path = Path::new(&compile_commands_dir).join("compile_commands.json"); + + // Parse compile_commands.json + let compile_commands: Vec = { + let data = fs::read_to_string(&compile_commands_path) + .expect("Failed to read compile_commands.json"); + serde_json::from_str(&data) + .expect("Failed to parse compile_commands.json") + }; + + // Directory of compile_commands.json, used to resolve relative paths + let base_dir = compile_commands_path + .parent() + .expect("Failed to get base directory of compile_commands.json"); + + // List of C files to compile (only base names) + let files_to_compile = vec![ + "mdns_networking_socket.c", + "log_write.c", + "log_timestamp.c", + "esp_netif_linux.c", + "freertos_linux.c", + "tag_log_level.c", + "log_linked_list.c", + "log_lock.c", + "log_level.c", + "log_binary_heap.c", + "esp_system_linux2.c", + "heap_caps_linux.c", + "mdns_stub.c", + "log_buffers.c", + "util.c" + ]; + + // Initialize the build + let mut build = cc::Build::new(); + +for file in &files_to_compile { + // Extract the base name from `file` for comparison + let target_base_name = Path::new(file) + .file_name() + .expect("Failed to extract base name from target file") + .to_str() + .expect("Target file name is not valid UTF-8"); + + // Find the entry in compile_commands.json by matching the base name + let cmd = compile_commands.iter() + .find(|entry| { + let full_path = Path::new(&entry.directory).join(&entry.file); // Resolve relative paths + if let Some(base_name) = full_path.file_name().and_then(|name| name.to_str()) { +// println!("Checking file: {} against {}", base_name, target_base_name); // Debug information + base_name == target_base_name + } else { + false + } + }) + .unwrap_or_else(|| panic!("{} not found in compile_commands.json", target_base_name)); + + // Add the file to the build + build.file(&cmd.file); + + // Parse flags and include paths from the command + for part in cmd.command.split_whitespace() { + if part.starts_with("-I") { + // Handle include directories + let include_path = &part[2..]; + let full_include_path = if Path::new(include_path).is_relative() { + base_dir.join(include_path).canonicalize() + .expect("Failed to resolve relative include path") + } else { + PathBuf::from(include_path) + }; + build.include(full_include_path); + } else if part.starts_with("-D") || part.starts_with("-std") { + // Add other compilation flags + build.flag(part); + } + } +} + + + + + // Compile with the gathered information + build.compile("mdns"); +} diff --git a/components/mdns/examples/simple_query/CMakeLists.txt b/components/mdns/examples/simple_query/CMakeLists.txt new file mode 100644 index 0000000000..a244a7cabb --- /dev/null +++ b/components/mdns/examples/simple_query/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.5) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +if(${IDF_TARGET} STREQUAL "linux") + set(EXTRA_COMPONENT_DIRS "../../../../common_components/linux_compat" "../../tests/host_test/components/") + set(COMPONENTS main) +endif() + +project(mdns_host) + +# Enable sanitizers only without console (we'd see some leaks on argtable when console exits) +if(NOT CONFIG_TEST_CONSOLE AND CONFIG_IDF_TARGET_LINUX) +idf_component_get_property(mdns mdns COMPONENT_LIB) +target_link_options(${mdns} INTERFACE -fsanitize=address -fsanitize=undefined) +endif() diff --git a/components/mdns/examples/simple_query/main/CMakeLists.txt b/components/mdns/examples/simple_query/main/CMakeLists.txt new file mode 100644 index 0000000000..31eb27ed13 --- /dev/null +++ b/components/mdns/examples/simple_query/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" "esp_system_linux2.c" + INCLUDE_DIRS + "." + REQUIRES mdns console nvs_flash) diff --git a/components/mdns/examples/simple_query/main/Kconfig.projbuild b/components/mdns/examples/simple_query/main/Kconfig.projbuild new file mode 100644 index 0000000000..9bf4bba235 --- /dev/null +++ b/components/mdns/examples/simple_query/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "Test Configuration" + + config TEST_HOSTNAME + string "mDNS Hostname" + default "esp32-mdns" + help + mDNS Hostname for example to use + + config TEST_NETIF_NAME + string "Network interface name" + default "eth2" + help + Name/ID if the network interface on which we run the mDNS host test + + config TEST_CONSOLE + bool "Start console" + default n + help + Test uses esp_console for interactive testing. + +endmenu diff --git a/components/mdns/examples/simple_query/main/esp_system_linux2.c b/components/mdns/examples/simple_query/main/esp_system_linux2.c new file mode 100644 index 0000000000..25ee652382 --- /dev/null +++ b/components/mdns/examples/simple_query/main/esp_system_linux2.c @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * All functions presented here are stubs for the POSIX/Linux implementation of FReeRTOS. + * They are meant to allow to compile, but they DO NOT return any meaningful value. + */ + +#include +#include +#include "esp_private/system_internal.h" +#include "esp_heap_caps.h" + +// dummy, we should never get here on Linux +void esp_restart_noos_dig(void) +{ + abort(); +} + +uint32_t esp_get_free_heap_size(void) +{ + return heap_caps_get_free_size(MALLOC_CAP_DEFAULT); +} + +uint32_t esp_get_free_internal_heap_size(void) +{ + return heap_caps_get_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); +} + +uint32_t esp_get_minimum_free_heap_size(void) +{ + return heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); +} + +const char *esp_get_idf_version(void) +{ + return "IDF_VER"; +} + +void __attribute__((noreturn)) esp_system_abort(const char *details) +{ + exit(1); +} diff --git a/components/mdns/examples/simple_query/main/idf_component.yml b/components/mdns/examples/simple_query/main/idf_component.yml new file mode 100644 index 0000000000..e2d4fe9ba2 --- /dev/null +++ b/components/mdns/examples/simple_query/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + idf: ">=5.0" + espressif/mdns: + version: "^1.0.0" + override_path: "../../.." + protocol_examples_common: + path: ${IDF_PATH}/examples/common_components/protocol_examples_common diff --git a/components/mdns/examples/simple_query/main/main.c b/components/mdns/examples/simple_query/main/main.c new file mode 100644 index 0000000000..1678678eef --- /dev/null +++ b/components/mdns/examples/simple_query/main/main.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_console.h" +#include "mdns.h" + +static const char *TAG = "mdns-test"; + +static esp_netif_t *s_netif; + +static void mdns_test_app(esp_netif_t *interface); + +#ifndef CONFIG_IDF_TARGET_LINUX +#include "protocol_examples_common.h" +#include "esp_event.h" +#include "nvs_flash.h" + +/** + * @brief This is an entry point for the real target device, + * need to init few components and connect to a network interface + */ +void app_main(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(example_connect()); + + mdns_test_app(EXAMPLE_INTERFACE); + + ESP_ERROR_CHECK(example_disconnect()); +} +#else + +/** + * @brief This is an entry point for the linux target (simulator on host) + * need to create a dummy WiFi station and use it as mdns network interface + */ +int main(int argc, char *argv[]) +{ + setvbuf(stdout, NULL, _IONBF, 0); + const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = CONFIG_TEST_NETIF_NAME }; + esp_netif_config_t cfg = { .base = &base_cg }; + s_netif = esp_netif_new(&cfg); + + mdns_test_app(s_netif); + + esp_netif_destroy(s_netif); + return 0; +} +#endif + +typedef size_t mdns_if_t; + +typedef struct { + mdns_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + struct pbuf *pb; + esp_ip_addr_t src; + esp_ip_addr_t dest; + uint16_t src_port; + uint8_t multicast; +} mdns_rx_packet_t; + +struct pbuf { + struct pbuf *next; + void *payload; + size_t tot_len; + size_t len; +}; + +esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len); +esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +void _mdns_packet_free(mdns_rx_packet_t *packet); + + +static void mdns_test_app(esp_netif_t *interface) +{ + uint8_t query_packet[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x64, 0x61, 0x76, 0x69, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, 0x00, 0x01, 0x00, 0x01}; + esp_ip_addr_t ip = ESP_IP4ADDR_INIT(224, 0, 0, 251); + +// ESP_ERROR_CHECK(mdns_init()); +// ESP_ERROR_CHECK(mdns_register_netif(interface)); +// ESP_ERROR_CHECK(mdns_netif_action(interface, MDNS_EVENT_ENABLE_IP4)); + esp_err_t err = _mdns_pcb_init(0, MDNS_IP_PROTOCOL_V4); + ESP_LOGI(TAG, "err = %d", err); + size_t len = _mdns_udp_pcb_write(0, MDNS_IP_PROTOCOL_V4, &ip, 5353, query_packet, sizeof(query_packet)); + ESP_LOGI(TAG, "len = %d", (int)len); +// query_mdns_host("david-work"); + vTaskDelay(pdMS_TO_TICKS(1000)); +// mdns_free(); + _mdns_pcb_deinit(0, MDNS_IP_PROTOCOL_V4); + ESP_LOGI(TAG, "Exit"); +} diff --git a/components/mdns/examples/simple_query/sdkconfig.defaults b/components/mdns/examples/simple_query/sdkconfig.defaults new file mode 100644 index 0000000000..dc79fbcad8 --- /dev/null +++ b/components/mdns/examples/simple_query/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_TEST_NETIF_NAME="eth0" +CONFIG_ESP_EVENT_POST_FROM_ISR=n +CONFIG_MDNS_NETWORKING_SOCKET=y +CONFIG_MDNS_SKIP_SUPPRESSING_OWN_QUERIES=y +CONFIG_MDNS_PREDEF_NETIF_STA=n +CONFIG_MDNS_PREDEF_NETIF_AP=n diff --git a/components/mdns/examples/usage.rs b/components/mdns/examples/usage.rs new file mode 100644 index 0000000000..68edcfd6c0 --- /dev/null +++ b/components/mdns/examples/usage.rs @@ -0,0 +1,58 @@ +// examples/basic_usage.rs + +use mdns::*; +use std::thread; +use std::time::Duration; + +pub fn test_mdns() { + let ip4 = EspIpAddr { + u_addr: EspIpUnion { + ip4: EspIp4Addr { + addr: u32::from_le_bytes([224, 0, 0, 251]), // Convert 224.0.0.251 to big-endian + }, + }, + addr_type: ESP_IPADDR_TYPE_V4, + }; + +// let query_packet: [u8; 34] = [ +// 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x64, 0x61, +// 0x76, 0x69, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, +// 0x00, 0x01, 0x00, 0x01, +// ]; + + if let Err(err) = mdns_pcb_init_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4) { + eprintln!("Failed to initialize mDNS PCB: {}", err); + return; + } + + let query_packet = create_mdns_query(); + println!("{:?}", query_packet); + + let len = mdns_udp_pcb_write_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4, ip4, 5353, &query_packet); + println!("Bytes sent: {}", len); + + thread::sleep(Duration::from_millis(500)); + + if let Err(err) = mdns_pcb_deinit_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4) { + eprintln!("Failed to deinitialize mDNS PCB: {}", err); + } +} + +fn main() { + // Initialize mDNS + mdns::mdns_init(); + +// // Query for a specific host +// mdns::mdns_query_host_rust("example.local"); + + // Deinitialize mDNS + mdns::mdns_deinit(); + + test_mdns(); + +// let result = mdns::mdns_pcb_init_rust(mdns::MdnsIf::Netif0, mdns::MdnsIpProtocol::Ip4); +// match result { +// Ok(_) => println!("mdns_pcb_init succeeded"), +// Err(err) => eprintln!("mdns_pcb_init failed with error code: {}", err), +// } +} diff --git a/components/mdns/mdns_stub.c b/components/mdns/mdns_stub.c new file mode 100644 index 0000000000..1bab517f75 --- /dev/null +++ b/components/mdns/mdns_stub.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_log.h" +#include "esp_console.h" +#include "mdns.h" + + +static const char *TAG = "mdns-stub"; +typedef size_t mdns_if_t; + +typedef struct { + mdns_if_t tcpip_if; + mdns_ip_protocol_t ip_protocol; + struct pbuf *pb; + esp_ip_addr_t src; + esp_ip_addr_t dest; + uint16_t src_port; + uint8_t multicast; +} mdns_rx_packet_t; + +struct pbuf { + struct pbuf *next; + void *payload; + size_t tot_len; + size_t len; +}; + +esp_err_t _mdns_pcb_init(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +size_t _mdns_udp_pcb_write(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol, const esp_ip_addr_t *ip, uint16_t port, uint8_t *data, size_t len); +esp_err_t _mdns_pcb_deinit(mdns_if_t tcpip_if, mdns_ip_protocol_t ip_protocol); +void _mdns_packet_free(mdns_rx_packet_t *packet); + +typedef void (*callback_t)(const uint8_t *, size_t len); + +static callback_t rust_callback = NULL; + + + +void set_callback(callback_t callback) +{ + rust_callback = callback; +} + +void set_callback2() +{ + ESP_LOGI(TAG, "set_callback2!"); +} + + +esp_err_t _mdns_send_rx_action(mdns_rx_packet_t *packet) +{ + ESP_LOGI(TAG, "Received packet!"); + ESP_LOG_BUFFER_HEXDUMP(TAG, packet->pb->payload, packet->pb->tot_len, ESP_LOG_INFO); + if (rust_callback) { + rust_callback(packet->pb->payload, packet->pb->tot_len); + } + _mdns_packet_free(packet); + return ESP_OK; +} + +esp_netif_t *g_netif = NULL; + + +esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if) +{ + if (g_netif == NULL) { + const esp_netif_inherent_config_t base_cg = { .if_key = "WIFI_STA_DEF", .if_desc = CONFIG_TEST_NETIF_NAME }; + esp_netif_config_t cfg = { .base = &base_cg }; + g_netif = esp_netif_new(&cfg); + } + return g_netif; +} diff --git a/components/mdns/src/lib.rs b/components/mdns/src/lib.rs new file mode 100644 index 0000000000..327cef0226 --- /dev/null +++ b/components/mdns/src/lib.rs @@ -0,0 +1,258 @@ +// src/lib.rs + +extern crate libc; +// extern crate trust_dns; +// use crate trust_dns; +use std::fmt; +use std::os::raw::c_char; +use std::ffi::CStr; +use std::fmt::Write; // For formatting strings +// use trust_dns_client::op::{Message, Query}; +// use trust_dns_client::rr::{RecordType, Name}; +// use trust_dns_client::serialize::binary::{BinDecodable, BinEncoder}; +use std::net::Ipv4Addr; +use dns_parser::{Builder, QueryClass, QueryType, Packet}; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIp4Addr { + pub addr: u32, // IPv4 address as a 32-bit integer +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIp6Addr { + pub addr: [u32; 4], // IPv6 address as an array of 4 32-bit integers + pub zone: u8, // Zone ID +} + +#[repr(C)] +pub union EspIpUnion { + pub ip4: EspIp4Addr, + pub ip6: EspIp6Addr, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIpAddr { + pub u_addr: EspIpUnion, // Union containing IPv4 or IPv6 address + pub addr_type: u8, +} + +// Manual implementation of Debug for the union +impl fmt::Debug for EspIpUnion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Safely access and format the union's members for debugging + unsafe { + write!(f, "EspIpUnion {{ ip4: {:?}, ip6: {:?} }}", self.ip4, self.ip6) + } + } +} + +// Manual implementation of Clone for the union +impl Clone for EspIpUnion { + fn clone(&self) -> Self { + // Safety: Assuming the union contains valid data in either `ip4` or `ip6` + unsafe { + EspIpUnion { + ip4: self.ip4.clone(), + } + } + } +} + +// Manual implementation of Copy for the union +impl Copy for EspIpUnion {} + +// // Other structs remain the same +// #[repr(C)] +// #[derive(Debug, Clone, Copy)] +// pub struct EspIp4Addr { +// addr: u32, +// } +// +// #[repr(C)] +// #[derive(Debug, Clone, Copy)] +// pub struct EspIp6Addr { +// addr: [u32; 4], +// zone: u8, +// } +// +// #[repr(C)] +// #[derive(Debug, Clone, Copy)] +// pub struct EspIpAddr { +// u_addr: EspIpUnion, // Union containing IPv4 or IPv6 address +// addr_type: u8, +// } +// Address type definitions +pub const ESP_IPADDR_TYPE_V4: u8 = 0; +pub const ESP_IPADDR_TYPE_V6: u8 = 6; +pub const ESP_IPADDR_TYPE_ANY: u8 = 46; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum MdnsIf { + Netif0 = 0, + // Add more as needed based on the actual C enum definition +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum MdnsIpProtocol { + Ip4 = 0, + Ip6 = 1, +} + +type EspErr = i32; + +extern "C" { + fn _mdns_pcb_init(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; + fn _mdns_udp_pcb_write( + tcpip_if: MdnsIf, + ip_protocol: MdnsIpProtocol, + ip: *const EspIpAddr, + port: u16, + data: *const u8, + len: usize, + ) -> usize; + fn _mdns_pcb_deinit(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; + fn set_callback(callback: extern "C" fn(*const u8, usize)); + +// fn set_callback2(); +} + +extern "C" fn rust_callback(data: *const u8, len: usize) +{ + println!("Received len: {}", len); + unsafe { + // Ensure that the data pointer is valid + if !data.is_null() { + // Create a Vec from the raw pointer and length + let data_vec = std::slice::from_raw_parts(data, len).to_vec(); + + // Now call the safe parser function with the Vec + parse_dns_response(&data_vec); } + } +} + +fn parse_dns_response(data: &[u8]) { + // Safe handling of the slice + println!("Parsing DNS response with length: {}", data.len()); + + parse_dns_response2(data); + // Process the data (this will be safe, as `data` is a slice) + // Example: You could convert the slice to a string, inspect it, or pass it to a DNS library +} + +fn parse_dns_response2(data: &[u8]) -> Result<(), String> { + println!("Parsing DNS response with length 2 : {}", data.len()); +// use dns_parser::Packet; + let packet = Packet::parse(&data).unwrap(); + for answer in packet.answers { + println!("{:?}", answer); + } +// match Message::from_vec(data) { +// Ok(msg) => { +// // Successful parsing +// println!("Parsed DNS message successfully."); +// } +// Err(e) => { +// // Detailed error message +// eprintln!("Error parsing DNS message: {}", e); +// } +// } + // Parse the response message +// let msg = Message::from_vec(data).map_err(|e| e.to_string())?; +// println!("Type: {}", msg.op_code().to_string()); +// // Check if the message is a response (opcode is Response) +// if msg.op_code() != trust_dns_client::op::OpCode::Status { +// return Err("Not a response message".to_string()); +// } +// +// // Display the answer section (which should contain A record) +// for answer in msg.answers() { +// println!("Non-IP answer: {:?}", answer); +// if let Some(ipv4_addr) = answer.rdata().to_ip_addr() { +// println!("Resolved IP address: {}", ipv4_addr); +// } else { +// println!("Non-IP answer: {:?}", answer); +// } +// } + + Ok(()) +} + +use std::ffi::CString; + + +pub fn mdns_init() { + println!("mdns_init called"); +} + +pub fn mdns_deinit() { + println!("mdns_deinit called"); +} + +pub fn create_mdns_query() -> Vec { + let query_name = "david-work.local"; // The domain you want to query + let query_type = QueryType::A; // Type A query for IPv4 address + let query_class = QueryClass::IN; // Class IN (Internet) + + // Create a new query with ID and recursion setting + let mut builder = Builder::new_query(12345, true); + + // Add the question for "david-work.local" + builder.add_question(query_name, false, query_type, query_class); + + // Build and return the query packet + builder.build().unwrap_or_else(|x| x) +} + +pub fn mdns_query_host_rust(name: &str) { + let c_name = CString::new(name).expect("Failed to create CString"); +// unsafe { +// mdns_query_host(c_name.as_ptr()); +// } +} + +pub fn mdns_pcb_init_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { + unsafe { + set_callback(rust_callback); +// set_callback2(); + } + + let err = unsafe { _mdns_pcb_init(tcpip_if, ip_protocol) }; + if err == 0 { + Ok(()) + } else { + Err(err) + } +} + +pub fn mdns_udp_pcb_write_rust( + tcpip_if: MdnsIf, + ip_protocol: MdnsIpProtocol, + ip: EspIpAddr, + port: u16, + data: &[u8], +) -> usize { + unsafe { + _mdns_udp_pcb_write( + tcpip_if, + ip_protocol, + &ip as *const EspIpAddr, + port, + data.as_ptr(), + data.len(), + ) + } +} + +pub fn mdns_pcb_deinit_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { + let err = unsafe { _mdns_pcb_deinit(tcpip_if, ip_protocol) }; + if err == 0 { + Ok(()) + } else { + Err(err) + } +} From 8f607c6829c8282f52e8e4b7387f279c8c3560be Mon Sep 17 00:00:00 2001 From: David Cermak Date: Wed, 27 Nov 2024 18:27:15 +0100 Subject: [PATCH 3/5] feat(mdns): Add default and ffi build features --- .github/workflows/mdns__rust.yml | 3 + components/mdns/CMakeLists.txt | 1 + components/mdns/Cargo.toml | 6 + components/mdns/build.rs | 17 ++- components/mdns/examples/usage.rs | 25 ++-- components/mdns/mdns.c | 4 +- components/mdns/src/lib.rs | 170 ++++++++++------------ components/mdns/src/service/ffi.rs | 190 +++++++++++++++++++++++++ components/mdns/src/service/mod.rs | 9 ++ components/mdns/src/service/native.rs | 161 +++++++++++++++++++++ components/mdns/src/service/service.rs | 12 ++ 11 files changed, 494 insertions(+), 104 deletions(-) create mode 100644 components/mdns/src/service/ffi.rs create mode 100644 components/mdns/src/service/mod.rs create mode 100644 components/mdns/src/service/native.rs create mode 100644 components/mdns/src/service/service.rs diff --git a/.github/workflows/mdns__rust.yml b/.github/workflows/mdns__rust.yml index a32d0ab88c..3124c5c96a 100644 --- a/.github/workflows/mdns__rust.yml +++ b/.github/workflows/mdns__rust.yml @@ -31,4 +31,7 @@ jobs: cd components/mdns/examples/simple_query/ idf.py build cd ../.. + # FFI build COMPILE_COMMANDS_DIR=examples/simple_query/build/ cargo run --example usage + # Default build + cargo run --example usage diff --git a/components/mdns/CMakeLists.txt b/components/mdns/CMakeLists.txt index 3a3b4c054b..60a13fd709 100644 --- a/components/mdns/CMakeLists.txt +++ b/components/mdns/CMakeLists.txt @@ -15,6 +15,7 @@ if(${target} STREQUAL "linux") set(dependencies esp_netif_linux esp_event) set(private_dependencies esp_timer console esp_system) set(srcs "mdns_stub.c" ${MDNS_NETWORKING} ${MDNS_CONSOLE}) + # set(srcs "mdns.c" ${MDNS_NETWORKING} ${MDNS_CONSOLE}) else() set(dependencies lwip console esp_netif) set(private_dependencies esp_timer esp_wifi) diff --git a/components/mdns/Cargo.toml b/components/mdns/Cargo.toml index 09a10e7458..420704dca5 100644 --- a/components/mdns/Cargo.toml +++ b/components/mdns/Cargo.toml @@ -6,8 +6,14 @@ edition = "2021" [dependencies] libc = "0.2" dns-parser = "0.8" +socket2 = "*" +nix = "0.26" +lazy_static = "*" [build-dependencies] cc = "1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" + +[features] +ffi = [] diff --git a/components/mdns/build.rs b/components/mdns/build.rs index 8647aa0c02..32c6e2893e 100644 --- a/components/mdns/build.rs +++ b/components/mdns/build.rs @@ -11,10 +11,23 @@ struct CompileCommand { } fn main() { + + println!("cargo:rerun-if-env-changed=COMPILE_COMMANDS_DIR"); // Get the directory for compile_commands.json from an environment variable - let compile_commands_dir = env::var("COMPILE_COMMANDS_DIR") - .unwrap_or_else(|_| ".".to_string()); // Default to current directory + let compile_commands_dir = match env::var("COMPILE_COMMANDS_DIR") { + Ok(dir) => dir, + Err(_) => { + // If the environment variable is not defined, return early + println!("COMPILE_COMMANDS_DIR not set, skipping custom build."); + // this is a native build + // println!("cargo:rustc-cfg=native"); + return; + } + }; + // building with FFI of mdns_networking + println!("COMPILE_COMMANDS_DIR set, enabling FFI feature."); + println!("cargo:rustc-cfg=feature=\"ffi\""); // Construct the path to the compile_commands.json file let compile_commands_path = Path::new(&compile_commands_dir).join("compile_commands.json"); diff --git a/components/mdns/examples/usage.rs b/components/mdns/examples/usage.rs index 68edcfd6c0..e82d998441 100644 --- a/components/mdns/examples/usage.rs +++ b/components/mdns/examples/usage.rs @@ -3,12 +3,16 @@ use mdns::*; use std::thread; use std::time::Duration; +use std::net::{UdpSocket, Ipv4Addr}; +use socket2::{Socket, Domain, Type, Protocol}; -pub fn test_mdns() { + + +fn test_mdns() { let ip4 = EspIpAddr { u_addr: EspIpUnion { ip4: EspIp4Addr { - addr: u32::from_le_bytes([224, 0, 0, 251]), // Convert 224.0.0.251 to big-endian + addr: u32::from_le_bytes([224, 0, 0, 251]), }, }, addr_type: ESP_IPADDR_TYPE_V4, @@ -25,11 +29,13 @@ pub fn test_mdns() { return; } - let query_packet = create_mdns_query(); - println!("{:?}", query_packet); + // let query_packet = create_mdns_query(); + // println!("{:?}", query_packet); + + - let len = mdns_udp_pcb_write_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4, ip4, 5353, &query_packet); - println!("Bytes sent: {}", len); + // let len = mdns_udp_pcb_write_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4, ip4, 5353, &query_packet); + // println!("Bytes sent: {}", len); thread::sleep(Duration::from_millis(500)); @@ -38,17 +44,20 @@ pub fn test_mdns() { } } + fn main() { // Initialize mDNS mdns::mdns_init(); // // Query for a specific host // mdns::mdns_query_host_rust("example.local"); - + mdns::mdns_query("david-work.local"); + thread::sleep(Duration::from_millis(500)); // Deinitialize mDNS mdns::mdns_deinit(); - test_mdns(); + + // test_mdns(); // let result = mdns::mdns_pcb_init_rust(mdns::MdnsIf::Netif0, mdns::MdnsIpProtocol::Ip4); // match result { diff --git a/components/mdns/mdns.c b/components/mdns/mdns.c index ca9d87ddbe..c59648ee76 100644 --- a/components/mdns/mdns.c +++ b/components/mdns/mdns.c @@ -176,7 +176,7 @@ static inline esp_netif_t *esp_netif_from_preset_if(mdns_predef_if_t predef_if) * @param tcpip_if Ordinal number of the interface * @return Pointer ot the esp_netif object if the interface is available, NULL otherwise */ -esp_netif_t *_mdns_get_esp_netif23(mdns_if_t tcpip_if) +esp_netif_t *_mdns_get_esp_netif(mdns_if_t tcpip_if) { if (tcpip_if < MDNS_MAX_INTERFACES) { if (s_esp_netifs[tcpip_if].netif == NULL && s_esp_netifs[tcpip_if].predefined) { @@ -347,7 +347,7 @@ static bool _mdns_can_add_more_services(void) return true; } -esp_err_t _mdns_send_rx_action23(mdns_rx_packet_t *packet) +esp_err_t _mdns_send_rx_action(mdns_rx_packet_t *packet) { mdns_action_t *action = NULL; diff --git a/components/mdns/src/lib.rs b/components/mdns/src/lib.rs index 327cef0226..4e30c53ac6 100644 --- a/components/mdns/src/lib.rs +++ b/components/mdns/src/lib.rs @@ -1,4 +1,6 @@ // src/lib.rs +mod service; +use service::{Service, NativeService, CService}; extern crate libc; // extern crate trust_dns; @@ -13,6 +15,29 @@ use std::fmt::Write; // For formatting strings use std::net::Ipv4Addr; use dns_parser::{Builder, QueryClass, QueryType, Packet}; + + +#[cfg(not(feature = "ffi"))] +fn build_info() { + println!("Default build"); +} + +#[cfg(not(feature = "ffi"))] +fn create_service(cb: fn(&[u8])) -> Box { + NativeService::init(cb) +} + + +#[cfg(feature = "ffi")] +fn build_info() { + println!("FFI build"); +} + +#[cfg(feature = "ffi")] +fn create_service(cb: fn(&[u8])) -> Box { + CService::init(cb) +} + #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct EspIp4Addr { @@ -64,26 +89,6 @@ impl Clone for EspIpUnion { // Manual implementation of Copy for the union impl Copy for EspIpUnion {} -// // Other structs remain the same -// #[repr(C)] -// #[derive(Debug, Clone, Copy)] -// pub struct EspIp4Addr { -// addr: u32, -// } -// -// #[repr(C)] -// #[derive(Debug, Clone, Copy)] -// pub struct EspIp6Addr { -// addr: [u32; 4], -// zone: u8, -// } -// -// #[repr(C)] -// #[derive(Debug, Clone, Copy)] -// pub struct EspIpAddr { -// u_addr: EspIpUnion, // Union containing IPv4 or IPv6 address -// addr_type: u8, -// } // Address type definitions pub const ESP_IPADDR_TYPE_V4: u8 = 0; pub const ESP_IPADDR_TYPE_V6: u8 = 6; @@ -117,8 +122,6 @@ extern "C" { ) -> usize; fn _mdns_pcb_deinit(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; fn set_callback(callback: extern "C" fn(*const u8, usize)); - -// fn set_callback2(); } extern "C" fn rust_callback(data: *const u8, len: usize) @@ -127,92 +130,92 @@ extern "C" fn rust_callback(data: *const u8, len: usize) unsafe { // Ensure that the data pointer is valid if !data.is_null() { - // Create a Vec from the raw pointer and length - let data_vec = std::slice::from_raw_parts(data, len).to_vec(); + // Create a Vec from the raw pointer and length + let data_vec = std::slice::from_raw_parts(data, len).to_vec(); - // Now call the safe parser function with the Vec - parse_dns_response(&data_vec); } + // Now call the safe parser function with the Vec + parse_dns_response(&data_vec).unwrap(); + } } } -fn parse_dns_response(data: &[u8]) { - // Safe handling of the slice - println!("Parsing DNS response with length: {}", data.len()); - - parse_dns_response2(data); - // Process the data (this will be safe, as `data` is a slice) - // Example: You could convert the slice to a string, inspect it, or pass it to a DNS library -} - -fn parse_dns_response2(data: &[u8]) -> Result<(), String> { +fn parse_dns_response(data: &[u8]) -> Result<(), String> { println!("Parsing DNS response with length 2 : {}", data.len()); -// use dns_parser::Packet; let packet = Packet::parse(&data).unwrap(); for answer in packet.answers { println!("{:?}", answer); } -// match Message::from_vec(data) { -// Ok(msg) => { -// // Successful parsing -// println!("Parsed DNS message successfully."); -// } -// Err(e) => { -// // Detailed error message -// eprintln!("Error parsing DNS message: {}", e); -// } -// } - // Parse the response message -// let msg = Message::from_vec(data).map_err(|e| e.to_string())?; -// println!("Type: {}", msg.op_code().to_string()); -// // Check if the message is a response (opcode is Response) -// if msg.op_code() != trust_dns_client::op::OpCode::Status { -// return Err("Not a response message".to_string()); -// } -// -// // Display the answer section (which should contain A record) -// for answer in msg.answers() { -// println!("Non-IP answer: {:?}", answer); -// if let Some(ipv4_addr) = answer.rdata().to_ip_addr() { -// println!("Resolved IP address: {}", ipv4_addr); -// } else { -// println!("Non-IP answer: {:?}", answer); -// } -// } - + for question in packet.questions { + println!("{:?}", question); + } Ok(()) } use std::ffi::CString; +use std::thread; +use std::time::Duration; +use lazy_static::lazy_static; +use std::sync::{Arc, Mutex}; +lazy_static! { + static ref SERVICE: Arc>>> = Arc::new(Mutex::new(None)); +} + +fn read_cb(vec: &[u8]) { + println!("Received {:?}", vec); + parse_dns_response(vec).unwrap(); +} pub fn mdns_init() { + build_info(); + let mut service_guard = SERVICE.lock().unwrap(); + if service_guard.is_none() { + // Initialize the service only if it hasn't been initialized + *service_guard = Some(create_service(read_cb)); + } + // let service: Box = create_service(read_cb); + // service.action1(); + // let packet: [u8; 34] = [ + // 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x64, 0x61, + // 0x76, 0x69, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, + // 0x00, 0x01, 0x00, 0x01, + // ]; + // service.send(packet.to_vec()); + // thread::sleep(Duration::from_millis(500)); + // service.deinit(); println!("mdns_init called"); } pub fn mdns_deinit() { + let mut service_guard = SERVICE.lock().unwrap(); + if let Some(service) = service_guard.take() { + service.deinit(); + } println!("mdns_deinit called"); } -pub fn create_mdns_query() -> Vec { - let query_name = "david-work.local"; // The domain you want to query +fn create_a_query(name: &str) -> Vec { let query_type = QueryType::A; // Type A query for IPv4 address let query_class = QueryClass::IN; // Class IN (Internet) // Create a new query with ID and recursion setting - let mut builder = Builder::new_query(12345, true); + let mut builder = Builder::new_query(0x5555, true); // Add the question for "david-work.local" - builder.add_question(query_name, false, query_type, query_class); + builder.add_question(name, false, query_type, query_class); // Build and return the query packet builder.build().unwrap_or_else(|x| x) } -pub fn mdns_query_host_rust(name: &str) { - let c_name = CString::new(name).expect("Failed to create CString"); -// unsafe { -// mdns_query_host(c_name.as_ptr()); -// } +pub fn mdns_query(name: &str) { + let service_guard = SERVICE.lock().unwrap(); + if let Some(service) = &*service_guard { + let packet = create_a_query(name); + service.send(packet); + } else { + println!("Service not initialized"); + } } pub fn mdns_pcb_init_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { @@ -229,24 +232,7 @@ pub fn mdns_pcb_init_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Resu } } -pub fn mdns_udp_pcb_write_rust( - tcpip_if: MdnsIf, - ip_protocol: MdnsIpProtocol, - ip: EspIpAddr, - port: u16, - data: &[u8], -) -> usize { - unsafe { - _mdns_udp_pcb_write( - tcpip_if, - ip_protocol, - &ip as *const EspIpAddr, - port, - data.as_ptr(), - data.len(), - ) - } -} + pub fn mdns_pcb_deinit_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { let err = unsafe { _mdns_pcb_deinit(tcpip_if, ip_protocol) }; diff --git a/components/mdns/src/service/ffi.rs b/components/mdns/src/service/ffi.rs new file mode 100644 index 0000000000..d9f1390fbc --- /dev/null +++ b/components/mdns/src/service/ffi.rs @@ -0,0 +1,190 @@ +use libc::AT_NULL; + +use super::service::Service; +use std::fmt; + +pub struct CService +{ + callback: fn(&[u8]) +} + +impl CService { + fn new(cb: fn(&[u8])) -> Self { + CService { + callback: cb + } + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIp4Addr { + pub addr: u32, // IPv4 address as a 32-bit integer +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIp6Addr { + pub addr: [u32; 4], // IPv6 address as an array of 4 32-bit integers + pub zone: u8, // Zone ID +} + +#[repr(C)] +pub union EspIpUnion { + pub ip4: EspIp4Addr, + pub ip6: EspIp6Addr, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct EspIpAddr { + pub u_addr: EspIpUnion, // Union containing IPv4 or IPv6 address + pub addr_type: u8, +} + +// Manual implementation of Debug for the union +impl fmt::Debug for EspIpUnion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Safely access and format the union's members for debugging + unsafe { + write!(f, "EspIpUnion {{ ip4: {:?}, ip6: {:?} }}", self.ip4, self.ip6) + } + } +} + +// Manual implementation of Clone for the union +impl Clone for EspIpUnion { + fn clone(&self) -> Self { + // Safety: Assuming the union contains valid data in either `ip4` or `ip6` + unsafe { + EspIpUnion { + ip4: self.ip4.clone(), + } + } + } +} + +// Manual implementation of Copy for the union +impl Copy for EspIpUnion {} + +// Address type definitions +pub const ESP_IPADDR_TYPE_V4: u8 = 0; +pub const ESP_IPADDR_TYPE_V6: u8 = 6; +pub const ESP_IPADDR_TYPE_ANY: u8 = 46; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum MdnsIf { + Netif0 = 0, + // Add more as needed based on the actual C enum definition +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub enum MdnsIpProtocol { + Ip4 = 0, + Ip6 = 1, +} + +type EspErr = i32; + +extern "C" { + fn _mdns_pcb_init(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; + fn _mdns_udp_pcb_write( + tcpip_if: MdnsIf, + ip_protocol: MdnsIpProtocol, + ip: *const EspIpAddr, + port: u16, + data: *const u8, + len: usize, + ) -> usize; + fn _mdns_pcb_deinit(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; + fn set_callback(callback: extern "C" fn(*const u8, usize)); +} + +static mut CALLBACK: Option = None; + +extern "C" fn rust_callback(data: *const u8, len: usize) +{ + println!("Received len: {}", len); + unsafe { + // Ensure that the data pointer is valid + if !data.is_null() { + // Create a Vec from the raw pointer and length + let data_vec = std::slice::from_raw_parts(data, len).to_vec(); + if let Some(cb) = CALLBACK { + cb(&data_vec); + } + // Now call the safe parser function with the Vec + // parse_dns_response(&data_vec); + } + } +} + + +pub fn mdns_udp_pcb_write_rust( + tcpip_if: MdnsIf, + ip_protocol: MdnsIpProtocol, + ip: EspIpAddr, + port: u16, + data: &[u8], +) -> usize { + unsafe { + _mdns_udp_pcb_write( + tcpip_if, + ip_protocol, + &ip as *const EspIpAddr, + port, + data.as_ptr(), + data.len(), + ) + } +} + +impl Service for CService { + + + fn init(cb:fn(&[u8])) -> Box { + println!("FfiHousekeeper: Initializing."); + unsafe { + set_callback(rust_callback); + CALLBACK = Some(cb); + } + let _ = unsafe { _mdns_pcb_init(MdnsIf::Netif0, MdnsIpProtocol::Ip4) }; + + + Box::new(CService::new(cb)) + } + + fn send(&self, packet: Vec) { + let ip4 = EspIpAddr { + u_addr: EspIpUnion { + ip4: EspIp4Addr { + addr: u32::from_le_bytes([224, 0, 0, 251]), + }, + }, + addr_type: ESP_IPADDR_TYPE_V4, + }; + println!("FfiHousekeeper: Sending"); + let len = mdns_udp_pcb_write_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4, ip4, 5353, &packet); + println!("Bytes sent: {}", len); + } + + fn action1(&self) { + println!("FfiHousekeeper: Performing Action1."); + } + + fn action2(&self) { + println!("FfiHousekeeper: Performing Action2."); + } + + fn action3(&self) { + println!("FfiHousekeeper: Performing Action3."); + } + + fn deinit(self: Box) { + let _ = unsafe { _mdns_pcb_deinit(MdnsIf::Netif0, MdnsIpProtocol::Ip4) }; + println!("FfiHousekeeper: Deinitializing."); + } + +} diff --git a/components/mdns/src/service/mod.rs b/components/mdns/src/service/mod.rs new file mode 100644 index 0000000000..9d64845a64 --- /dev/null +++ b/components/mdns/src/service/mod.rs @@ -0,0 +1,9 @@ +mod service; // Import the trait definition +mod native; // Thread-based implementation +mod ffi; // FFI-based implementation + +pub use service::Service; // Expose the trait +pub use native::NativeService; +pub use ffi::CService; +// pub use thread::ThreadHousekeeper; // Expose the thread-based implementation +// pub use ffi::FfiHousekeeper; // Expose the FFI-based implementation diff --git a/components/mdns/src/service/native.rs b/components/mdns/src/service/native.rs new file mode 100644 index 0000000000..04c9ebfb24 --- /dev/null +++ b/components/mdns/src/service/native.rs @@ -0,0 +1,161 @@ +use super::service::Service; +use std::thread; + +use std::net::{UdpSocket, Ipv4Addr}; +use dns_parser::rdata::null; +use socket2::{Socket, Domain, Type, Protocol}; +use nix::unistd::{pipe, read, write, close}; +use nix::sys::select::{select, FdSet}; +use nix::sys::time::TimeVal; +use std::os::fd::{AsRawFd, FromRawFd}; +use std::mem::MaybeUninit; + +enum Action { + Action1, + Action2, + Action3, +} + +pub struct NativeService +{ + handle: Option>, + socket: UdpSocket, + write_fd: i32, + callback: fn(&[u8]), +} + +fn create_multicast_socket() -> UdpSocket { + let addr: std::net::SocketAddr = "0.0.0.0:5353".parse().unwrap(); + + let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::UDP)).unwrap(); + socket.set_reuse_address(true).unwrap(); + socket.bind(&addr.into()).unwrap(); + + let multicast_addr = Ipv4Addr::new(224, 0, 0, 251); + let interface = Ipv4Addr::new(0, 0, 0, 0); + socket.join_multicast_v4(&multicast_addr, &interface).unwrap(); + + socket.into() +} + +impl NativeService +{ + fn new(cb: fn(&[u8])) -> Self { + let socket = create_multicast_socket(); + let (read_fd, w_fd) = pipe().expect("Failed to create pipe"); + let local_cb = cb; + let local_socket = socket.try_clone().unwrap(); + + let handle = thread::spawn(move || { + + loop { + let socket_fd = local_socket.as_raw_fd(); + let mut read_fds = FdSet::new(); + read_fds.insert(socket_fd); + read_fds.insert(read_fd); + + + let mut timeout = TimeVal::new(0, 100_000); + + match select(read_fd.max(socket_fd) + 1, Some(&mut read_fds), None, None, Some(&mut timeout)) { + Ok(0) => println!("ThreadHousekeeper: Performing housekeeping tasks"), + Ok(_) => { + if read_fds.contains(socket_fd) { + // let mut buf: [MaybeUninit; 1500] = unsafe { MaybeUninit::uninit().assume_init() }; + // let mut buf: [u8; 1500]; + let mut buf = vec![0u8; 1500]; // Create a buffer using a vector + // let sock = unsafe { Socket::from_raw_fd(socket_fd) }; + match local_socket.recv_from(&mut buf) { + Ok((size, addr)) => { + // Convert the buffer from MaybeUninit to a regular slice of u8 + // let buf = unsafe { + // std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, size) + // }; + // println!("Received {} bytes from {:?}: {:?}", size, addr, buf); + local_cb(&buf[..size]); + } + Err(e) => println!("Error reading from socket: {:?}", e), + } + } + + if read_fds.contains(read_fd) { + let mut buf = [0u8; 10]; + match read(read_fd, &mut buf) { + Ok(size) => { + println!("{}", size); + if size == 0 { + break; + } + } + Err(e) => println!("Error reading from socket: {:?}", e), + } + } + } + Err(e) => { + println!("Error in select(): {:?}", e); + break; + } + } + } + + // close(read_fd).expect("Failed to close read end of pipe"); + }); + + NativeService { + handle: Some(handle), + socket, + write_fd: w_fd, + callback: cb + } + } +} + +impl Service for NativeService { + + fn init(cb: fn(&[u8])) -> Box { + println!("Native: Initializing."); + Box::new(NativeService::new(cb)) + } + + fn send(&self, packet: Vec) { + let destination: std::net::SocketAddr = "224.0.0.251:5353".parse().unwrap(); + self.socket.send_to(&packet, &destination) + .expect("Failed to send mDNS query"); + } + + fn action1(&self) { + let buf: [u8; 1] = [0x01]; + // self.socket.send(&buf).unwrap(); + write(self.write_fd, &buf).unwrap(); + // if let Some(tx) = &self.tx { + // tx.send(Action::Action1).unwrap(); + // } + } + + fn action2(&self) { + // if let Some(tx) = &self.tx { + // tx.send(Action::Action2).unwrap(); + // } + } + + fn action3(&self) { + // if let Some(tx) = &self.tx { + // tx.send(Action::Action3).unwrap(); + // } + } + + fn deinit(self: Box) { + println!("DEINIT called"); + // if let Some(tx) = self.tx { + // drop(tx); + // } + close(self.write_fd).unwrap(); + + if let Some(handle) = self.handle { + handle.join().unwrap(); + println!("NativeService: Deinitialized"); + } + println!("DEINIT done..."); + + } +} diff --git a/components/mdns/src/service/service.rs b/components/mdns/src/service/service.rs new file mode 100644 index 0000000000..8653611a1c --- /dev/null +++ b/components/mdns/src/service/service.rs @@ -0,0 +1,12 @@ +pub trait Service: Send + Sync { + fn init(cb: fn(&[u8])) -> Box + where + Self: Sized; + + fn send(&self, packet: Vec); + fn action1(&self); + fn action2(&self); + fn action3(&self); + + fn deinit(self: Box); +} From 180510343bfbb538b40405a175dba7b67f0fa68c Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 6 Dec 2024 08:57:09 +0100 Subject: [PATCH 4/5] feat(mdns): Start implementing querier --- components/mdns/examples/usage.rs | 57 +----- components/mdns/src/lib.rs | 273 +++++++++++--------------- components/mdns/src/service/native.rs | 4 +- 3 files changed, 124 insertions(+), 210 deletions(-) diff --git a/components/mdns/examples/usage.rs b/components/mdns/examples/usage.rs index e82d998441..49fa9e9775 100644 --- a/components/mdns/examples/usage.rs +++ b/components/mdns/examples/usage.rs @@ -3,65 +3,14 @@ use mdns::*; use std::thread; use std::time::Duration; -use std::net::{UdpSocket, Ipv4Addr}; -use socket2::{Socket, Domain, Type, Protocol}; - - - -fn test_mdns() { - let ip4 = EspIpAddr { - u_addr: EspIpUnion { - ip4: EspIp4Addr { - addr: u32::from_le_bytes([224, 0, 0, 251]), - }, - }, - addr_type: ESP_IPADDR_TYPE_V4, - }; - -// let query_packet: [u8; 34] = [ -// 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x64, 0x61, -// 0x76, 0x69, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, -// 0x00, 0x01, 0x00, 0x01, -// ]; - - if let Err(err) = mdns_pcb_init_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4) { - eprintln!("Failed to initialize mDNS PCB: {}", err); - return; - } - - // let query_packet = create_mdns_query(); - // println!("{:?}", query_packet); - - - - // let len = mdns_udp_pcb_write_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4, ip4, 5353, &query_packet); - // println!("Bytes sent: {}", len); - - thread::sleep(Duration::from_millis(500)); - - if let Err(err) = mdns_pcb_deinit_rust(MdnsIf::Netif0, MdnsIpProtocol::Ip4) { - eprintln!("Failed to deinitialize mDNS PCB: {}", err); - } -} fn main() { // Initialize mDNS - mdns::mdns_init(); + mdns_init(); -// // Query for a specific host -// mdns::mdns_query_host_rust("example.local"); - mdns::mdns_query("david-work.local"); + mdns_query("david-work.local"); thread::sleep(Duration::from_millis(500)); // Deinitialize mDNS - mdns::mdns_deinit(); - - - // test_mdns(); - -// let result = mdns::mdns_pcb_init_rust(mdns::MdnsIf::Netif0, mdns::MdnsIpProtocol::Ip4); -// match result { -// Ok(_) => println!("mdns_pcb_init succeeded"), -// Err(err) => eprintln!("mdns_pcb_init failed with error code: {}", err), -// } + mdns_deinit(); } diff --git a/components/mdns/src/lib.rs b/components/mdns/src/lib.rs index 4e30c53ac6..4b8c5e6e2e 100644 --- a/components/mdns/src/lib.rs +++ b/components/mdns/src/lib.rs @@ -1,20 +1,10 @@ // src/lib.rs mod service; use service::{Service, NativeService, CService}; - -extern crate libc; -// extern crate trust_dns; -// use crate trust_dns; -use std::fmt; -use std::os::raw::c_char; -use std::ffi::CStr; -use std::fmt::Write; // For formatting strings -// use trust_dns_client::op::{Message, Query}; -// use trust_dns_client::rr::{RecordType, Name}; -// use trust_dns_client::serialize::binary::{BinDecodable, BinEncoder}; -use std::net::Ipv4Addr; +use lazy_static::lazy_static; +use std::sync::{Arc, Mutex}; use dns_parser::{Builder, QueryClass, QueryType, Packet}; - +use std::time::{Duration, Instant}; #[cfg(not(feature = "ffi"))] @@ -27,7 +17,6 @@ fn create_service(cb: fn(&[u8])) -> Box { NativeService::init(cb) } - #[cfg(feature = "ffi")] fn build_info() { println!("FFI build"); @@ -38,127 +27,135 @@ fn create_service(cb: fn(&[u8])) -> Box { CService::init(cb) } -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct EspIp4Addr { - pub addr: u32, // IPv4 address as a 32-bit integer +fn parse_dns_response(data: &[u8]) -> Result<(), String> { + println!("Parsing DNS response with length 2 : {}", data.len()); + let packet = Packet::parse(&data).unwrap(); + for answer in packet.answers { + println!("{:?}", answer); + } + for question in packet.questions { + println!("{:?}", question); + } + Ok(()) } -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct EspIp6Addr { - pub addr: [u32; 4], // IPv6 address as an array of 4 32-bit integers - pub zone: u8, // Zone ID -} -#[repr(C)] -pub union EspIpUnion { - pub ip4: EspIp4Addr, - pub ip6: EspIp6Addr, +// pub trait Querier: Send + Sync { +// fn init() -> Box +// where +// Self: Sized; + +// fn deinit(self: Box); +// } + + +#[derive(Debug)] +pub struct Query { + pub name: String, + pub service: String, + pub proto: String, + pub query_type: QueryType, + pub unicast: bool, + pub timeout: Duration, + pub added_at: Instant, // To track when the query was added } -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct EspIpAddr { - pub u_addr: EspIpUnion, // Union containing IPv4 or IPv6 address - pub addr_type: u8, +pub struct Querier { + queries: Vec, } -// Manual implementation of Debug for the union -impl fmt::Debug for EspIpUnion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // Safely access and format the union's members for debugging - unsafe { - write!(f, "EspIpUnion {{ ip4: {:?}, ip6: {:?} }}", self.ip4, self.ip6) - } - } +fn create_querier() -> Box { + NativeService::init(cb) } -// Manual implementation of Clone for the union -impl Clone for EspIpUnion { - fn clone(&self) -> Self { - // Safety: Assuming the union contains valid data in either `ip4` or `ip6` - unsafe { - EspIpUnion { - ip4: self.ip4.clone(), - } +impl Querier { + pub fn new() -> Self { + Self { + queries: Vec::new(), } } -} -// Manual implementation of Copy for the union -impl Copy for EspIpUnion {} - -// Address type definitions -pub const ESP_IPADDR_TYPE_V4: u8 = 0; -pub const ESP_IPADDR_TYPE_V6: u8 = 6; -pub const ESP_IPADDR_TYPE_ANY: u8 = 46; - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub enum MdnsIf { - Netif0 = 0, - // Add more as needed based on the actual C enum definition -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub enum MdnsIpProtocol { - Ip4 = 0, - Ip6 = 1, -} + pub fn init(&mut self) { + println!("Querier initialized"); + } -type EspErr = i32; + pub fn deinit(&mut self) { + self.queries.clear(); + println!("Querier deinitialized"); + } -extern "C" { - fn _mdns_pcb_init(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; - fn _mdns_udp_pcb_write( - tcpip_if: MdnsIf, - ip_protocol: MdnsIpProtocol, - ip: *const EspIpAddr, - port: u16, - data: *const u8, - len: usize, - ) -> usize; - fn _mdns_pcb_deinit(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> EspErr; - fn set_callback(callback: extern "C" fn(*const u8, usize)); -} + pub fn add( + &mut self, + name: String, + service: String, + proto: String, + query_type: QueryType, + unicast: bool, + timeout: Duration, + // semaphore: Option, + ) -> usize { + let query = Query { + name, + service, + proto, + query_type, + unicast, + timeout, + // semaphore, + added_at: Instant::now(), + }; + self.queries.push(query); + self.queries.len() - 1 // Return the ID (index of the query) + } -extern "C" fn rust_callback(data: *const u8, len: usize) -{ - println!("Received len: {}", len); - unsafe { - // Ensure that the data pointer is valid - if !data.is_null() { - // Create a Vec from the raw pointer and length - let data_vec = std::slice::from_raw_parts(data, len).to_vec(); + pub fn process(&mut self) { + let now = Instant::now(); + self.queries.retain(|query| { + let elapsed = now.duration_since(query.added_at); + if elapsed > query.timeout { + println!("Query timed out: {:?}", query); + // if let Some(semaphore) = &query.semaphore { + // semaphore.add_permits(1); // Release semaphore if waiting + // } + false // Remove the query + } else { + println!("Processing query: {:?}", query); + // Implement retry logic here if needed + true // Keep the query + } + }); + } - // Now call the safe parser function with the Vec - parse_dns_response(&data_vec).unwrap(); + pub async fn wait(&mut self, id: usize) -> Result<(), &'static str> { + if let Some(query) = self.queries.get_mut(id) { + Ok(()) + // if let Some(semaphore) = &query.semaphore { + // semaphore.acquire().await.unwrap(); // Block until the semaphore is released + // Ok(()) + // } else { + // Err("No semaphore set for this query") + // } + } else { + Err("Invalid query ID") } } } -fn parse_dns_response(data: &[u8]) -> Result<(), String> { - println!("Parsing DNS response with length 2 : {}", data.len()); - let packet = Packet::parse(&data).unwrap(); - for answer in packet.answers { - println!("{:?}", answer); - } - for question in packet.questions { - println!("{:?}", question); - } - Ok(()) +struct Objects { + service: Option>, + // responder: Option>, + querier: Option, } -use std::ffi::CString; -use std::thread; -use std::time::Duration; -use lazy_static::lazy_static; -use std::sync::{Arc, Mutex}; +// lazy_static! { +// static ref SERVICE: Arc>>> = Arc::new(Mutex::new(None)); +// } lazy_static! { - static ref SERVICE: Arc>>> = Arc::new(Mutex::new(None)); + static ref SERVER: Arc> = Arc::new(Mutex::new(Objects { + service: None, + querier: None, + })); } fn read_cb(vec: &[u8]) { @@ -168,27 +165,22 @@ fn read_cb(vec: &[u8]) { pub fn mdns_init() { build_info(); - let mut service_guard = SERVICE.lock().unwrap(); - if service_guard.is_none() { + let mut service_guard = SERVER.lock().unwrap(); + if service_guard.service.is_none() { // Initialize the service only if it hasn't been initialized - *service_guard = Some(create_service(read_cb)); + service_guard.service = Some(create_service(read_cb)); } - // let service: Box = create_service(read_cb); - // service.action1(); - // let packet: [u8; 34] = [ - // 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x64, 0x61, - // 0x76, 0x69, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, - // 0x00, 0x01, 0x00, 0x01, - // ]; - // service.send(packet.to_vec()); - // thread::sleep(Duration::from_millis(500)); - // service.deinit(); + if service_guard.querier.is_none() { + // Initialize the service only if it hasn't been initialized + service_guard.querier = Some(init()); + } + println!("mdns_init called"); } pub fn mdns_deinit() { - let mut service_guard = SERVICE.lock().unwrap(); - if let Some(service) = service_guard.take() { + let mut service_guard = SERVER.lock().unwrap(); + if let Some(service) = service_guard.service.take() { service.deinit(); } println!("mdns_deinit called"); @@ -209,36 +201,11 @@ fn create_a_query(name: &str) -> Vec { } pub fn mdns_query(name: &str) { - let service_guard = SERVICE.lock().unwrap(); - if let Some(service) = &*service_guard { + let service_guard = SERVER.lock().unwrap(); + if let Some(service) = &service_guard.service { let packet = create_a_query(name); service.send(packet); } else { println!("Service not initialized"); } } - -pub fn mdns_pcb_init_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { - unsafe { - set_callback(rust_callback); -// set_callback2(); - } - - let err = unsafe { _mdns_pcb_init(tcpip_if, ip_protocol) }; - if err == 0 { - Ok(()) - } else { - Err(err) - } -} - - - -pub fn mdns_pcb_deinit_rust(tcpip_if: MdnsIf, ip_protocol: MdnsIpProtocol) -> Result<(), EspErr> { - let err = unsafe { _mdns_pcb_deinit(tcpip_if, ip_protocol) }; - if err == 0 { - Ok(()) - } else { - Err(err) - } -} diff --git a/components/mdns/src/service/native.rs b/components/mdns/src/service/native.rs index 04c9ebfb24..da530836c4 100644 --- a/components/mdns/src/service/native.rs +++ b/components/mdns/src/service/native.rs @@ -2,13 +2,11 @@ use super::service::Service; use std::thread; use std::net::{UdpSocket, Ipv4Addr}; -use dns_parser::rdata::null; use socket2::{Socket, Domain, Type, Protocol}; use nix::unistd::{pipe, read, write, close}; use nix::sys::select::{select, FdSet}; use nix::sys::time::TimeVal; -use std::os::fd::{AsRawFd, FromRawFd}; -use std::mem::MaybeUninit; +use std::os::fd::AsRawFd; enum Action { Action1, From 07b53fc021a0dbca79314154460017f10718904e Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 13 Dec 2024 17:32:57 +0100 Subject: [PATCH 5/5] fix(mdns): Querier with cond-var WIP --- components/mdns/examples/usage.rs | 5 +- components/mdns/src/lib.rs | 266 +++++++++++--------------- components/mdns/src/querier.rs | 148 ++++++++++++++ components/mdns/src/service/native.rs | 9 +- 4 files changed, 266 insertions(+), 162 deletions(-) create mode 100644 components/mdns/src/querier.rs diff --git a/components/mdns/examples/usage.rs b/components/mdns/examples/usage.rs index 49fa9e9775..366daed45c 100644 --- a/components/mdns/examples/usage.rs +++ b/components/mdns/examples/usage.rs @@ -1,16 +1,17 @@ // examples/basic_usage.rs +// use std::process::Termination; use mdns::*; use std::thread; use std::time::Duration; - +// use libc::__c_anonymous_xsk_tx_metadata_union; fn main() { // Initialize mDNS mdns_init(); mdns_query("david-work.local"); - thread::sleep(Duration::from_millis(500)); + thread::sleep(Duration::from_millis(1500)); // Deinitialize mDNS mdns_deinit(); } diff --git a/components/mdns/src/lib.rs b/components/mdns/src/lib.rs index 4b8c5e6e2e..67c1cdc8f7 100644 --- a/components/mdns/src/lib.rs +++ b/components/mdns/src/lib.rs @@ -1,180 +1,93 @@ -// src/lib.rs mod service; +mod querier; + use service::{Service, NativeService, CService}; +use querier::Querier; + use lazy_static::lazy_static; use std::sync::{Arc, Mutex}; use dns_parser::{Builder, QueryClass, QueryType, Packet}; -use std::time::{Duration, Instant}; - +use std::time::Duration; -#[cfg(not(feature = "ffi"))] -fn build_info() { - println!("Default build"); +lazy_static! { + static ref SERVER: Arc> = Arc::new(Mutex::new(Objects { + service: None, + querier: None, + })); } -#[cfg(not(feature = "ffi"))] -fn create_service(cb: fn(&[u8])) -> Box { - NativeService::init(cb) +struct Objects { + service: Option>, + querier: Option, } -#[cfg(feature = "ffi")] fn build_info() { - println!("FFI build"); + #[cfg(not(feature = "ffi"))] + { + println!("Default build"); + } + #[cfg(feature = "ffi")] + { + println!("FFI build"); + } } -#[cfg(feature = "ffi")] fn create_service(cb: fn(&[u8])) -> Box { - CService::init(cb) + #[cfg(not(feature = "ffi"))] + { + NativeService::init(cb) + } + #[cfg(feature = "ffi")] + { + CService::init(cb) + } +} + +fn read_cb(vec: &[u8]) { + if vec.len() == 0 { + let mut service_guard = SERVER.lock().unwrap(); + if let Some(querier) = &mut service_guard.querier { + println!("querier process {:?}", vec); + let packet = querier.process(); + if packet.is_some() { + if let Some(service) = &service_guard.service { + service.send(packet.unwrap()); + } + } + } + } else { + println!("Received {:?}", vec); + let mut service_guard = SERVER.lock().unwrap(); + if let Some(querier) = &mut service_guard.querier { + querier.parse(&vec).expect("Failed to parse.."); + } + // parse_dns_response(vec).unwrap(); + } } fn parse_dns_response(data: &[u8]) -> Result<(), String> { println!("Parsing DNS response with length 2 : {}", data.len()); - let packet = Packet::parse(&data).unwrap(); + let packet = Packet::parse(data).unwrap(); for answer in packet.answers { + println!("ANSWER:"); println!("{:?}", answer); } for question in packet.questions { + println!("QUESTION:"); println!("{:?}", question); } Ok(()) } - -// pub trait Querier: Send + Sync { -// fn init() -> Box -// where -// Self: Sized; - -// fn deinit(self: Box); -// } - - -#[derive(Debug)] -pub struct Query { - pub name: String, - pub service: String, - pub proto: String, - pub query_type: QueryType, - pub unicast: bool, - pub timeout: Duration, - pub added_at: Instant, // To track when the query was added -} - -pub struct Querier { - queries: Vec, -} - -fn create_querier() -> Box { - NativeService::init(cb) -} - -impl Querier { - pub fn new() -> Self { - Self { - queries: Vec::new(), - } - } - - pub fn init(&mut self) { - println!("Querier initialized"); - } - - pub fn deinit(&mut self) { - self.queries.clear(); - println!("Querier deinitialized"); - } - - pub fn add( - &mut self, - name: String, - service: String, - proto: String, - query_type: QueryType, - unicast: bool, - timeout: Duration, - // semaphore: Option, - ) -> usize { - let query = Query { - name, - service, - proto, - query_type, - unicast, - timeout, - // semaphore, - added_at: Instant::now(), - }; - self.queries.push(query); - self.queries.len() - 1 // Return the ID (index of the query) - } - - pub fn process(&mut self) { - let now = Instant::now(); - self.queries.retain(|query| { - let elapsed = now.duration_since(query.added_at); - if elapsed > query.timeout { - println!("Query timed out: {:?}", query); - // if let Some(semaphore) = &query.semaphore { - // semaphore.add_permits(1); // Release semaphore if waiting - // } - false // Remove the query - } else { - println!("Processing query: {:?}", query); - // Implement retry logic here if needed - true // Keep the query - } - }); - } - - pub async fn wait(&mut self, id: usize) -> Result<(), &'static str> { - if let Some(query) = self.queries.get_mut(id) { - Ok(()) - // if let Some(semaphore) = &query.semaphore { - // semaphore.acquire().await.unwrap(); // Block until the semaphore is released - // Ok(()) - // } else { - // Err("No semaphore set for this query") - // } - } else { - Err("Invalid query ID") - } - } -} - -struct Objects { - service: Option>, - // responder: Option>, - querier: Option, -} - -// lazy_static! { -// static ref SERVICE: Arc>>> = Arc::new(Mutex::new(None)); -// } - -lazy_static! { - static ref SERVER: Arc> = Arc::new(Mutex::new(Objects { - service: None, - querier: None, - })); -} - -fn read_cb(vec: &[u8]) { - println!("Received {:?}", vec); - parse_dns_response(vec).unwrap(); -} - pub fn mdns_init() { build_info(); let mut service_guard = SERVER.lock().unwrap(); if service_guard.service.is_none() { - // Initialize the service only if it hasn't been initialized service_guard.service = Some(create_service(read_cb)); } if service_guard.querier.is_none() { - // Initialize the service only if it hasn't been initialized - service_guard.querier = Some(init()); + service_guard.querier = Some(Querier::new()); } - println!("mdns_init called"); } @@ -186,26 +99,63 @@ pub fn mdns_deinit() { println!("mdns_deinit called"); } -fn create_a_query(name: &str) -> Vec { - let query_type = QueryType::A; // Type A query for IPv4 address - let query_class = QueryClass::IN; // Class IN (Internet) - - // Create a new query with ID and recursion setting - let mut builder = Builder::new_query(0x5555, true); +/* +pub fn mdns_query(name: &str) { + let mut service_guard = SERVER.lock().unwrap(); - // Add the question for "david-work.local" - builder.add_question(name, false, query_type, query_class); + if let Some(querier) = &mut service_guard.querier { + let timeout = Duration::from_secs(5); + let query_id = querier.add( + name.to_string(), + "".to_string(), + "_http._tcp".to_string(), + QueryType::A, + false, + timeout, + ); + querier.wait(query_id).await.unwrap(); + println!("Query added with ID: {}", query_id); + } - // Build and return the query packet - builder.build().unwrap_or_else(|x| x) } + */ + pub fn mdns_query(name: &str) { - let service_guard = SERVER.lock().unwrap(); - if let Some(service) = &service_guard.service { - let packet = create_a_query(name); - service.send(packet); - } else { - println!("Service not initialized"); + // Lock the server to access the querier + let query_id; + let querier_cvar; + + { + let mut service_guard = SERVER.lock().unwrap(); + if let Some(querier) = &mut service_guard.querier { + let timeout = Duration::from_secs(1); + query_id = querier.add( + name.to_string(), + "".to_string(), + "_http._tcp".to_string(), + QueryType::A, + false, + timeout, + ); + querier_cvar = querier.completed_queries.clone(); // Clone the Arc pair + } else { + println!("No querier available"); + return; + } + } // Release the SERVER lock here + + // Wait for the query to complete + let (lock, cvar) = &*querier_cvar; + let mut completed = lock.lock().unwrap(); + while !completed.get(&query_id).copied().unwrap_or(false) { + let result = cvar.wait_timeout(completed, Duration::from_secs(5)).unwrap(); + completed = result.0; // Update the lock guard + if result.1.timed_out() { + println!("Query timed out: ID {}", query_id); + return; + } } + + println!("Query completed!!! ID {}", query_id); } diff --git a/components/mdns/src/querier.rs b/components/mdns/src/querier.rs new file mode 100644 index 0000000000..6ff3e769a0 --- /dev/null +++ b/components/mdns/src/querier.rs @@ -0,0 +1,148 @@ +use dns_parser::{Builder, QueryClass, QueryType, Packet}; +use std::sync::{Arc, Condvar, Mutex}; +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Query { + pub name: String, + pub service: String, + pub proto: String, + pub query_type: QueryType, + pub unicast: bool, + pub timeout: Duration, + pub added_at: Instant, + pub packet: Vec, + pub id: usize, +} + +pub struct Querier { + queries: Vec, + pub(crate) completed_queries: Arc<(Mutex>, Condvar)>, // Shared state for query completion +} + +impl Querier { + pub fn new() -> Self { + Self { + queries: Vec::new(), + completed_queries: Arc::new((Mutex::new(HashMap::new()), Condvar::new())), + } + } + + pub fn add( + &mut self, + name: String, + service: String, + proto: String, + query_type: QueryType, + unicast: bool, + timeout: Duration, + ) -> usize { + let id = self.queries.len(); + let query = Query { + name: name.clone(), + service, + proto, + query_type, + unicast, + timeout, + added_at: Instant::now(), + packet: create_a_query(&name), + id: id.clone() + }; + self.queries.push(query); + self.completed_queries.0.lock().unwrap().insert(id, false); // Mark as incomplete + id + } + pub fn parse(&mut self, data: &[u8]) -> Result<(), String> { + println!("Parsing DNS response with length 2 : {}", data.len()); + let packet = Packet::parse(data).unwrap(); + for answer in packet.answers { + println!("ANSWER:"); + println!("{:?}", answer); + let name = answer.name.to_string(); + let mut completed_queries = vec![]; + self.queries.retain(|query| { + if query.name == name { + println!("ANSWER: {:?}", answer.data); + completed_queries.push(query.id); // Track for completion + false + } + else { true } + }); + let (lock, cvar) = &*self.completed_queries; + let mut completed = lock.lock().unwrap(); + for query_id in completed_queries { + if let Some(entry) = completed.get_mut(&query_id) { + *entry = true; + } + } + cvar.notify_all(); + + } + for question in packet.questions { + println!("{:?}", question); + } + Ok(()) + + } + + pub fn process(&mut self) -> Option> { + let now = Instant::now(); + let mut packet_to_send: Option> = None; + + // Collect IDs of timed-out queries to mark them as complete + let mut timed_out_queries = vec![]; + self.queries.retain(|query| { + let elapsed = now.duration_since(query.added_at); + if elapsed > query.timeout { + timed_out_queries.push(query.id); // Track for completion + false // Remove the query + } else { + packet_to_send = Some(query.packet.clone()); + true // Keep the query + } + }); + + // Mark timed-out queries as complete and notify waiting threads + let (lock, cvar) = &*self.completed_queries; + let mut completed = lock.lock().unwrap(); + for query_id in timed_out_queries { + if let Some(entry) = completed.get_mut(&query_id) { + *entry = true; + } + } + cvar.notify_all(); + println!("Processing... query"); + + packet_to_send + } + pub fn wait(&self, id: usize) -> Result<(), &'static str> { + let (lock, cvar) = &*self.completed_queries; + + // Wait until the query is marked as complete or timeout expires + let mut completed = lock.lock().unwrap(); + while !completed.get(&id).copied().unwrap_or(false) { + completed = cvar.wait(completed).unwrap(); + } + Ok(()) + } + + fn mark_query_as_complete(&self, query: &Query) { + let (lock, cvar) = &*self.completed_queries; + let mut completed = lock.lock().unwrap(); + if let Some(entry) = completed.get_mut(&(self.queries.len() - 1)) { + *entry = true; + } + cvar.notify_all(); + } +} + +fn create_a_query(name: &str) -> Vec { + let query_type = QueryType::A; // Type A query for IPv4 address + let query_class = QueryClass::IN; // Class IN (Internet) + + let mut builder = Builder::new_query(0, true); + builder.add_question(name, false, query_type, query_class); + builder.build().unwrap_or_else(|x| x) +} diff --git a/components/mdns/src/service/native.rs b/components/mdns/src/service/native.rs index da530836c4..458e024911 100644 --- a/components/mdns/src/service/native.rs +++ b/components/mdns/src/service/native.rs @@ -7,6 +7,7 @@ use nix::unistd::{pipe, read, write, close}; use nix::sys::select::{select, FdSet}; use nix::sys::time::TimeVal; use std::os::fd::AsRawFd; +use std::ptr::null; enum Action { Action1, @@ -53,10 +54,14 @@ impl NativeService read_fds.insert(read_fd); - let mut timeout = TimeVal::new(0, 100_000); + let mut timeout = TimeVal::new(0, 500_000); match select(read_fd.max(socket_fd) + 1, Some(&mut read_fds), None, None, Some(&mut timeout)) { - Ok(0) => println!("ThreadHousekeeper: Performing housekeeping tasks"), + Ok(0) => { + println!("ThreadHousekeeper: Performing housekeeping tasks"); + let buf = vec![]; + local_cb(&buf); + } Ok(_) => { if read_fds.contains(socket_fd) { // let mut buf: [MaybeUninit; 1500] = unsafe { MaybeUninit::uninit().assume_init() };