From 67639b58a2d5313f487fd092b9d7e5c9ed597ac7 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 22 Nov 2024 16:47:06 +0100 Subject: [PATCH] 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..cc66ad7461 --- /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 + rustc --version + cargo --version + . ${IDF_PATH}/export.sh + cd components/mdns/examples/simple_query/ + idf.py build + cd ../.. + rustup + 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) + } +}