├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── clang-format-check.yml │ ├── sanitizers.yml │ └── upload-to-component-registry.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── CMakeLists.txt ├── esp32 │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── app_main.c │ │ ├── audio.c │ │ ├── camera.c │ │ └── idf_component.yml │ ├── partitions.csv │ └── sdkconfig.defaults ├── generic │ ├── CMakeLists.txt │ ├── main.c │ ├── reader.c │ └── reader.h ├── pico │ ├── .gitignore │ ├── CMakeLists.txt │ ├── FreeRTOSConfig.h │ ├── FreeRTOS_Kernel_import.cmake │ ├── README.md │ ├── config.h │ ├── lwipopts.h │ ├── main.c │ ├── mbedtls_config.h │ └── netinet │ │ └── in.h └── raspberrypi │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ └── main.c ├── idf_component.yml ├── include ├── peer.h ├── peer_connection.h └── peer_signaling.h ├── src ├── CMakeLists.txt ├── address.c ├── address.h ├── agent.c ├── agent.h ├── base64.c ├── base64.h ├── buffer.c ├── buffer.h ├── config.h ├── dtls_srtp.c ├── dtls_srtp.h ├── ice.c ├── ice.h ├── mdns.c ├── mdns.h ├── peer.c ├── peer.h ├── peer_connection.c ├── peer_connection.h ├── peer_signaling.c ├── peer_signaling.h ├── ports.c ├── ports.h ├── rtcp.c ├── rtcp.h ├── rtp.c ├── rtp.h ├── sctp.c ├── sctp.h ├── sdp.c ├── sdp.h ├── socket.c ├── socket.h ├── ssl_transport.c ├── ssl_transport.h ├── stun.c ├── stun.h ├── utils.c └── utils.h └── tests ├── CMakeLists.txt ├── test_agent.c ├── test_dtls.c ├── test_peer_connection.c ├── test_rb.c ├── test_sdp.c └── test_stun.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | ColumnLimit: 0 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: sepfy95 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | submodules: recursive 14 | - name: install 15 | run: > 16 | sudo apt-get update && sudo apt-get --no-install-recommends -y install 17 | cmake 18 | - name: build 19 | run: > 20 | mkdir cmake ; 21 | cd cmake ; 22 | cmake .. -DENABLE_TESTS=true ; 23 | make -j$(nproc); 24 | run-parts ./tests/ 25 | -------------------------------------------------------------------------------- /.github/workflows/clang-format-check.yml: -------------------------------------------------------------------------------- 1 | name: clang-format Check 2 | on: [push, pull_request] 3 | jobs: 4 | formatting-check: 5 | name: Formatting Check 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | path: 10 | - 'examples' 11 | - 'src' 12 | - 'tests' 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Run clang-format style check for C/C++/Protobuf programs. 16 | uses: jidicula/clang-format-action@v4.13.0 17 | with: 18 | clang-format-version: '17' 19 | check-path: ${{ matrix.path }} 20 | -------------------------------------------------------------------------------- /.github/workflows/sanitizers.yml: -------------------------------------------------------------------------------- 1 | name: sanitizers 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-22.04 8 | env: 9 | CC: clang 10 | CXX: clang++ 11 | strategy: 12 | matrix: 13 | sanitizer: 14 | - ADDRESS_SANITIZER 15 | - MEMORY_SANITIZER 16 | - THREAD_SANITIZER 17 | - UNDEFINED_BEHAVIOR_SANITIZER 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: install 24 | run: sudo apt-get update && sudo apt-get --no-install-recommends -y install cmake clang 25 | 26 | - name: build 27 | run: > 28 | mkdir cmake ; 29 | cd cmake ; 30 | cmake .. -DENABLE_TESTS=true -D${{matrix.sanitizer}}=true; 31 | make -j$(nproc); 32 | run-parts ./tests/ || true; 33 | -------------------------------------------------------------------------------- /.github/workflows/upload-to-component-registry.yml: -------------------------------------------------------------------------------- 1 | name: Upload component to https://components.espressif.com 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | jobs: 7 | upload_components: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | submodules: "recursive" 13 | - name: Upload component to the component registry 14 | uses: espressif/upload-components-ci-action@v1 15 | with: 16 | name: "libpeer" 17 | version: ${{ github.ref_name }} 18 | namespace: "sepfy" 19 | api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/libsrtp"] 2 | path = third_party/libsrtp 3 | url = https://github.com/cisco/libsrtp.git 4 | [submodule "third_party/cJSON"] 5 | path = third_party/cJSON 6 | url = https://github.com/DaveGamble/cJSON.git 7 | [submodule "third_party/usrsctp"] 8 | path = third_party/usrsctp 9 | url = https://github.com/sctplab/usrsctp.git 10 | [submodule "third_party/mbedtls"] 11 | path = third_party/mbedtls 12 | url = https://github.com/Mbed-TLS/mbedtls.git 13 | [submodule "third_party/coreHTTP"] 14 | path = third_party/coreHTTP 15 | url = https://github.com/FreeRTOS/coreHTTP 16 | [submodule "third_party/coreMQTT"] 17 | path = third_party/coreMQTT 18 | url = https://github.com/FreeRTOS/coreMQTT 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | include(${CMAKE_CURRENT_LIST_DIR}/third_party/coreHTTP/httpFilePaths.cmake) 4 | include(${CMAKE_CURRENT_LIST_DIR}/third_party/coreMQTT/mqttFilePaths.cmake) 5 | 6 | if (DEFINED ENV{IDF_PATH}) 7 | file(GLOB ESP32_CODES "./src/*.c") 8 | idf_component_register( 9 | SRCS ${ESP32_CODES} ${HTTP_SOURCES} ${MQTT_SOURCES} ${MQTT_SERIALIZER_SOURCES} 10 | INCLUDE_DIRS "./include" ${HTTP_INCLUDE_PUBLIC_DIRS} ${MQTT_INCLUDE_PUBLIC_DIRS} 11 | PRIV_INCLUDE_DIRS "./src" 12 | REQUIRES mbedtls srtp json esp_netif 13 | ) 14 | add_definitions("-DCONFIG_USE_LWIP=1" "-DCONFIG_USE_USRSCTP=0" "-DCONFIG_AUDIO_BUFFER_SIZE=8096" "-DCONFIG_DATA_BUFFER_SIZE=102400" "-D__BYTE_ORDER=__LITTLE_ENDIAN" "-DHTTP_DO_NOT_USE_CUSTOM_CONFIG" "-DMQTT_DO_NOT_USE_CUSTOM_CONFIG") 15 | return() 16 | endif() 17 | 18 | project(peer) 19 | 20 | option(ENABLE_TESTS "Enable tests" OFF) 21 | option(BUILD_SHARED_LIBS "Build shared libraries" ON) 22 | option(ADDRESS_SANITIZER "Build with AddressSanitizer." OFF) 23 | option(MEMORY_SANITIZER "Build with MemorySanitizer." OFF) 24 | option(THREAD_SANITIZER "Build with ThreadSanitizer." OFF) 25 | option(UNDEFINED_BEHAVIOR_SANITIZER "Build with UndefinedBehaviorSanitizer." OFF) 26 | 27 | include(ExternalProject) 28 | 29 | include_directories(${CMAKE_BINARY_DIR}/dist/include ${CMAKE_BINARY_DIR}/dist/include/cjson) 30 | 31 | link_directories(${CMAKE_BINARY_DIR}/dist/lib) 32 | 33 | set(DEP_LIBS "srtp2" "usrsctp" "mbedtls" "mbedcrypto" "mbedx509" "cjson") 34 | # Extended debug information (symbols, source code, and macro definitions) 35 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g3") 36 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3") 37 | 38 | function(enableSanitizer SANITIZER) 39 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fsanitize=${SANITIZER} -fno-omit-frame-pointer -fno-optimize-sibling-calls" PARENT_SCOPE) 40 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${SANITIZER}" PARENT_SCOPE) 41 | endfunction() 42 | 43 | if(ADDRESS_SANITIZER) 44 | enableSanitizer("address") 45 | endif() 46 | 47 | if(MEMORY_SANITIZER) 48 | enableSanitizer("memory") 49 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-memory-track-origins") 50 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize-memory-track-origins") 51 | endif() 52 | 53 | if(THREAD_SANITIZER) 54 | enableSanitizer("thread") 55 | endif() 56 | 57 | if(UNDEFINED_BEHAVIOR_SANITIZER) 58 | enableSanitizer("undefined") 59 | endif() 60 | 61 | add_definitions("-Wunused-variable -Werror=sequence-point -Werror=pointer-sign -Werror=return-type -Werror=sizeof-pointer-memaccess -Wincompatible-pointer-types -DHTTP_DO_NOT_USE_CUSTOM_CONFIG -DMQTT_DO_NOT_USE_CUSTOM_CONFIG") 62 | 63 | add_subdirectory(src) 64 | add_subdirectory(examples) 65 | 66 | if(ENABLE_TESTS) 67 | add_subdirectory(tests) 68 | endif() 69 | 70 | ExternalProject_Add(cjson 71 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/cJSON 72 | CMAKE_ARGS 73 | -DCMAKE_C_FLAGS="-fPIC" 74 | -DBUILD_SHARED_LIBS=off 75 | -DENABLE_CJSON_TEST=off 76 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/dist 77 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 78 | ) 79 | 80 | ExternalProject_Add(mbedtls 81 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/mbedtls 82 | CMAKE_ARGS 83 | -DCMAKE_C_FLAGS="-fPIC" 84 | -DENABLE_TESTING=off 85 | -DENABLE_PROGRAMS=off 86 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/dist 87 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 88 | ) 89 | file(READ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/mbedtls/include/mbedtls/mbedtls_config.h INPUT_CONTENT) 90 | string(REPLACE "//#define MBEDTLS_SSL_DTLS_SRTP" "#define MBEDTLS_SSL_DTLS_SRTP" MODIFIED_CONTENT ${INPUT_CONTENT}) 91 | file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/mbedtls/include/mbedtls/mbedtls_config.h ${MODIFIED_CONTENT}) 92 | 93 | ExternalProject_Add(srtp2 94 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libsrtp 95 | CMAKE_ARGS 96 | -DCMAKE_C_FLAGS="-fPIC" 97 | -DTEST_APPS=off 98 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/dist 99 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 100 | ) 101 | 102 | ExternalProject_Add(usrsctp 103 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/usrsctp 104 | CMAKE_ARGS 105 | -DCMAKE_C_FLAGS="-fPIC" 106 | -Dsctp_build_programs=off 107 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/dist 108 | -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} 109 | ) 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sepfy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libpeer - Portable WebRTC Library for IoT/Embedded Device 2 | 3 | ![build](https://github.com/sepfy/pear/actions/workflows/build.yml/badge.svg) 4 | 5 | libpeer is a WebRTC implementation written in C, developed with BSD socket. The library aims to integrate IoT/Embedded device video/audio streaming with WebRTC, such as ESP32 and Raspberry Pi 6 | 7 | ### Features 8 | 9 | - Vdieo/Audio Codec 10 | - H264 11 | - G.711 PCM (A-law) 12 | - G.711 PCM (µ-law) 13 | - OPUS 14 | - DataChannel 15 | - STUN/TURN 16 | - IPV4/IPV6 17 | - Signaling 18 | - [WHIP](https://www.ietf.org/archive/id/draft-ietf-wish-whip-01.html) 19 | - MQTT 20 | 21 | ### Dependencies 22 | 23 | * [mbedtls](https://github.com/Mbed-TLS/mbedtls) 24 | * [libsrtp](https://github.com/cisco/libsrtp) 25 | * [usrsctp](https://github.com/sctplab/usrsctp) 26 | * [cJSON](https://github.com/DaveGamble/cJSON.git) 27 | * [coreHTTP](https://github.com/FreeRTOS/coreHTTP) 28 | * [coreMQTT](https://github.com/FreeRTOS/coreMQTT) 29 | 30 | ### Getting Started with Generic Example 31 | - Copy URL from the test [website](https://sepfy.github.io/libpeer) 32 | - Build and run the example 33 | ```bash 34 | $ sudo apt -y install git cmake 35 | $ git clone --recursive https://github.com/sepfy/libpeer 36 | $ cd libpeer 37 | $ cmake -S . -B build && cmake --build build 38 | $ wget http://www.live555.com/liveMedia/public/264/test.264 # Download test video file 39 | $ wget https://mauvecloud.net/sounds/alaw08m.wav # Download test audio file 40 | $ ./examples/generic/sample -u 41 | ``` 42 | - Click Connect button on the website 43 | 44 | ### Examples for Platforms 45 | - [ESP32](https://github.com/sepfy/libpeer/tree/main/examples/esp32): MJPEG over datachannel 46 | - [PICO](https://github.com/sepfy/libpeer/tree/main/examples/pico): Ping pong with datachannel 47 | - [Raspberry Pi](https://github.com/sepfy/libpeer/tree/main/examples/raspberrypi): Video and two-way audio stream 48 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(examples) 2 | 3 | add_subdirectory(generic) 4 | 5 | -------------------------------------------------------------------------------- /examples/esp32/.gitignore: -------------------------------------------------------------------------------- 1 | components/srtp 2 | build 3 | dependencies.lock 4 | managed_components 5 | sdkconfig 6 | sdkconfig.old 7 | -------------------------------------------------------------------------------- /examples/esp32/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following four lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(esp32-peer) 7 | 8 | # Uncomment if using usrsctp. 9 | #idf_build_set_property(COMPILE_OPTIONS "-DHAVE_USRSCTP" APPEND) 10 | -------------------------------------------------------------------------------- /examples/esp32/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 2 | This guide demonstrates how to stream JPEG images over a WebRTC data channel using ESP32 hardware. 3 | 4 | ## Supported Devices 5 | | Device |Image| 6 | |---|---| 7 | | [Freenove ESP32-S3-WROOM Board](https://store.freenove.com/products/fnk0085) || 8 | | [XIAO ESP32S3 Sense](https://wiki.seeedstudio.com/xiao_esp32s3_getting_started/) || 9 | | [ESP32-EYE](https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP-EYE_Getting_Started_Guide.md) || 10 | | [M5Camera](https://github.com/m5stack/M5Stack-Camera) || 11 | 12 | ## Setup Instructions 13 | 14 | ### Install esp-idf (v5.2 or higher) 15 | ```bash 16 | $ git clone -b v5.2.2 https://github.com/espressif/esp-idf.git 17 | $ cd esp-idf 18 | $ source install.sh 19 | $ source export.sh 20 | ``` 21 | 22 | ### Download Required Libraries 23 | ```bash 24 | $ git clone https://github.com/sepfy/libpeer 25 | $ cd libpeer/examples/esp32 26 | ``` 27 | 28 | ### Get Your URL 29 | Go to the test [website](https://sepfy.github.io/libpeer) and copy the URL 30 | 31 | ### Configure Your Build 32 | ```bash 33 | $ idf.py menuconfig 34 | # Peer Connection Configuration -> Signaling URL 35 | # Example Connection Configuration -> WiFi SSID and WiFi Password 36 | ``` 37 | 38 | ### Build the Project 39 | ```bash 40 | $ idf.py build 41 | ``` 42 | 43 | ### Flash and Test 44 | ```bash 45 | $ idf.py flash 46 | ``` 47 | ### Connect to Your Device 48 | Back to the test website and click Connect button 49 | 50 | -------------------------------------------------------------------------------- /examples/esp32/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS 2 | "app_main.c" "camera.c" "audio.c" 3 | INCLUDE_DIRS "." 4 | ) 5 | 6 | target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format") 7 | -------------------------------------------------------------------------------- /examples/esp32/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Peer Example Configuration" 2 | 3 | choice ESP32_CAMERA_MODEL 4 | prompt "Camera model" 5 | default ESP32_EYE 6 | help 7 | Select the camera model to use. 8 | config ESP32_EYE 9 | bool "ESP32-EYE" 10 | config ESP32S3_EYE 11 | bool "ESP32S3-EYE" 12 | config ESP32_M5STACK_CAMERA_B 13 | bool "M5STACK-CAMERA-B" 14 | config ESP32S3_XIAO_SENSE 15 | bool "ESP32S3-XIAO-SENSE" 16 | endchoice 17 | 18 | config SIGNALING_URL 19 | string "Signaling URL" 20 | default "https://libpeer.com/public/test" 21 | help 22 | Enter the URL of the Peer library to use. 23 | config SIGNALING_TOKEN 24 | string "Signaling Token" 25 | default "" 26 | help 27 | Enter the token of the Peer library to use. 28 | endmenu 29 | -------------------------------------------------------------------------------- /examples/esp32/main/app_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "esp_event.h" 8 | #include "esp_log.h" 9 | #include "esp_mac.h" 10 | #include "esp_netif.h" 11 | #include "esp_ota_ops.h" 12 | #include "esp_partition.h" 13 | #include "esp_system.h" 14 | #include "esp_tls.h" 15 | #include "freertos/FreeRTOS.h" 16 | #include "nvs_flash.h" 17 | #include "protocol_examples_common.h" 18 | 19 | #include "peer.h" 20 | 21 | static const char* TAG = "webrtc"; 22 | 23 | static TaskHandle_t xPcTaskHandle = NULL; 24 | static TaskHandle_t xCameraTaskHandle = NULL; 25 | static TaskHandle_t xAudioTaskHandle = NULL; 26 | 27 | extern esp_err_t camera_init(); 28 | extern esp_err_t audio_init(); 29 | extern void camera_task(void* pvParameters); 30 | extern void audio_task(void* pvParameters); 31 | 32 | SemaphoreHandle_t xSemaphore = NULL; 33 | 34 | PeerConnection* g_pc; 35 | PeerConnectionState eState = PEER_CONNECTION_CLOSED; 36 | int gDataChannelOpened = 0; 37 | 38 | int64_t get_timestamp() { 39 | struct timeval tv; 40 | gettimeofday(&tv, NULL); 41 | return (tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL)); 42 | } 43 | 44 | static void oniceconnectionstatechange(PeerConnectionState state, void* user_data) { 45 | ESP_LOGI(TAG, "PeerConnectionState: %d", state); 46 | eState = state; 47 | // not support datachannel close event 48 | if (eState != PEER_CONNECTION_COMPLETED) { 49 | gDataChannelOpened = 0; 50 | } 51 | } 52 | 53 | static void onmessage(char* msg, size_t len, void* userdata, uint16_t sid) { 54 | ESP_LOGI(TAG, "Datachannel message: %.*s", len, msg); 55 | } 56 | 57 | void onopen(void* userdata) { 58 | ESP_LOGI(TAG, "Datachannel opened"); 59 | gDataChannelOpened = 1; 60 | } 61 | 62 | static void onclose(void* userdata) { 63 | } 64 | 65 | void peer_connection_task(void* arg) { 66 | ESP_LOGI(TAG, "peer_connection_task started"); 67 | 68 | for (;;) { 69 | if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) { 70 | peer_connection_loop(g_pc); 71 | xSemaphoreGive(xSemaphore); 72 | } 73 | 74 | vTaskDelay(pdMS_TO_TICKS(1)); 75 | } 76 | } 77 | 78 | void app_main(void) { 79 | PeerConfiguration config = { 80 | .ice_servers = { 81 | {.urls = "stun:stun.l.google.com:19302"}}, 82 | .audio_codec = CODEC_PCMA, 83 | .datachannel = DATA_CHANNEL_BINARY, 84 | }; 85 | 86 | ESP_LOGI(TAG, "[APP] Startup.."); 87 | ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); 88 | ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); 89 | 90 | esp_log_level_set("*", ESP_LOG_INFO); 91 | esp_log_level_set("esp-tls", ESP_LOG_VERBOSE); 92 | esp_log_level_set("MQTT_CLIENT", ESP_LOG_VERBOSE); 93 | esp_log_level_set("MQTT_EXAMPLE", ESP_LOG_VERBOSE); 94 | esp_log_level_set("TRANSPORT_BASE", ESP_LOG_VERBOSE); 95 | esp_log_level_set("TRANSPORT", ESP_LOG_VERBOSE); 96 | esp_log_level_set("OUTBOX", ESP_LOG_VERBOSE); 97 | 98 | ESP_ERROR_CHECK(nvs_flash_init()); 99 | ESP_ERROR_CHECK(esp_netif_init()); 100 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 101 | ESP_ERROR_CHECK(example_connect()); 102 | 103 | xSemaphore = xSemaphoreCreateMutex(); 104 | 105 | peer_init(); 106 | 107 | camera_init(); 108 | 109 | #if defined(CONFIG_ESP32S3_XIAO_SENSE) 110 | audio_init(); 111 | #endif 112 | 113 | g_pc = peer_connection_create(&config); 114 | peer_connection_oniceconnectionstatechange(g_pc, oniceconnectionstatechange); 115 | peer_connection_ondatachannel(g_pc, onmessage, onopen, onclose); 116 | peer_signaling_connect(CONFIG_SIGNALING_URL, CONFIG_SIGNALING_TOKEN, g_pc); 117 | 118 | #if defined(CONFIG_ESP32S3_XIAO_SENSE) 119 | StackType_t* stack_memory = (StackType_t*)heap_caps_malloc(8192 * sizeof(StackType_t), MALLOC_CAP_SPIRAM); 120 | StaticTask_t task_buffer; 121 | if (stack_memory) { 122 | xAudioTaskHandle = xTaskCreateStaticPinnedToCore(audio_task, "audio", 8192, NULL, 7, stack_memory, &task_buffer, 0); 123 | } 124 | #endif 125 | 126 | xTaskCreatePinnedToCore(camera_task, "camera", 4096, NULL, 8, &xCameraTaskHandle, 1); 127 | 128 | xTaskCreatePinnedToCore(peer_connection_task, "peer_connection", 8192, NULL, 5, &xPcTaskHandle, 1); 129 | 130 | ESP_LOGI(TAG, "[APP] Free memory: %d bytes", esp_get_free_heap_size()); 131 | printf("============= Configuration =============\n"); 132 | printf(" %-5s : %s\n", "URL", CONFIG_SIGNALING_URL); 133 | printf(" %-5s : %s\n", "Token", CONFIG_SIGNALING_TOKEN); 134 | printf("=========================================\n"); 135 | 136 | while (1) { 137 | peer_signaling_loop(); 138 | vTaskDelay(pdMS_TO_TICKS(10)); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /examples/esp32/main/audio.c: -------------------------------------------------------------------------------- 1 | #include "driver/i2s_pdm.h" 2 | #include "esp_log.h" 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/task.h" 5 | 6 | #include "esp_audio_enc.h" 7 | #include "esp_audio_enc_default.h" 8 | #include "esp_audio_enc_reg.h" 9 | #include "esp_g711_enc.h" 10 | 11 | #include "peer_connection.h" 12 | 13 | #define I2S_CLK_GPIO 42 14 | #define I2S_DATA_GPIO 41 15 | 16 | static const char* TAG = "AUDIO"; 17 | 18 | extern PeerConnection* g_pc; 19 | extern PeerConnectionState eState; 20 | extern int get_timestamp(); 21 | 22 | i2s_chan_handle_t rx_handle = NULL; 23 | 24 | esp_audio_enc_handle_t enc_handle = NULL; 25 | esp_audio_enc_in_frame_t aenc_in_frame = {0}; 26 | esp_audio_enc_out_frame_t aenc_out_frame = {0}; 27 | esp_g711_enc_config_t g711_cfg; 28 | esp_audio_enc_config_t enc_cfg; 29 | 30 | esp_err_t audio_codec_init() { 31 | uint8_t* read_buf = NULL; 32 | uint8_t* write_buf = NULL; 33 | int read_size = 0; 34 | int out_size = 0; 35 | 36 | esp_audio_err_t ret = ESP_AUDIO_ERR_OK; 37 | 38 | esp_audio_enc_register_default(); 39 | 40 | g711_cfg.sample_rate = ESP_AUDIO_SAMPLE_RATE_8K; 41 | g711_cfg.channel = ESP_AUDIO_MONO; 42 | g711_cfg.bits_per_sample = ESP_AUDIO_BIT16; 43 | 44 | enc_cfg.type = ESP_AUDIO_TYPE_G711A; 45 | enc_cfg.cfg = &g711_cfg; 46 | enc_cfg.cfg_sz = sizeof(g711_cfg); 47 | 48 | ret = esp_audio_enc_open(&enc_cfg, &enc_handle); 49 | if (ret != ESP_AUDIO_ERR_OK) { 50 | ESP_LOGE(TAG, "audio encoder open failed"); 51 | return ESP_FAIL; 52 | } 53 | 54 | int frame_size = (g711_cfg.bits_per_sample * g711_cfg.channel) >> 3; 55 | // Get frame_size 56 | esp_audio_enc_get_frame_size(enc_handle, &read_size, &out_size); 57 | ESP_LOGI(TAG, "audio codec init. frame size: %d, read size: %d, out size: %d", frame_size, read_size, out_size); 58 | // 8000HZ duration 20ms 59 | if (frame_size == read_size) { 60 | read_size *= 8000 / 1000 * 20; 61 | out_size *= 8000 / 1000 * 20; 62 | } 63 | read_buf = malloc(read_size); 64 | write_buf = malloc(out_size); 65 | if (read_buf == NULL || write_buf == NULL) { 66 | return ESP_FAIL; 67 | } 68 | 69 | aenc_in_frame.buffer = read_buf; 70 | aenc_in_frame.len = read_size; 71 | aenc_out_frame.buffer = write_buf; 72 | aenc_out_frame.len = out_size; 73 | 74 | ESP_LOGI(TAG, "audio codec init done. in buffer size: %d, out buffer size: %d", read_size, out_size); 75 | return 0; 76 | } 77 | 78 | esp_err_t audio_init(void) { 79 | i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); 80 | ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); 81 | 82 | i2s_pdm_rx_config_t pdm_rx_cfg = { 83 | .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(8000), 84 | .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), 85 | .gpio_cfg = { 86 | .clk = I2S_CLK_GPIO, 87 | .din = I2S_DATA_GPIO, 88 | .invert_flags = { 89 | .clk_inv = false, 90 | }, 91 | }, 92 | }; 93 | 94 | ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg)); 95 | ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); 96 | 97 | return audio_codec_init(); 98 | } 99 | 100 | void audio_deinit(void) { 101 | ESP_ERROR_CHECK(i2s_channel_disable(rx_handle)); 102 | ESP_ERROR_CHECK(i2s_del_channel(rx_handle)); 103 | } 104 | 105 | int32_t audio_get_samples(uint8_t* buf, size_t size) { 106 | size_t bytes_read; 107 | 108 | if (i2s_channel_read(rx_handle, (char*)buf, size, &bytes_read, 1000) != ESP_OK) { 109 | ESP_LOGE(TAG, "i2s read error"); 110 | } 111 | 112 | return bytes_read; 113 | } 114 | 115 | void audio_task(void* arg) { 116 | int ret; 117 | static int64_t last_time; 118 | int64_t curr_time; 119 | float bytes = 0; 120 | 121 | last_time = get_timestamp(); 122 | ESP_LOGI(TAG, "audio task started"); 123 | 124 | for (;;) { 125 | if (eState == PEER_CONNECTION_COMPLETED) { 126 | ret = audio_get_samples(aenc_in_frame.buffer, aenc_in_frame.len); 127 | 128 | if (ret == aenc_in_frame.len) { 129 | if (esp_audio_enc_process(enc_handle, &aenc_in_frame, &aenc_out_frame) == ESP_AUDIO_ERR_OK) { 130 | peer_connection_send_audio(g_pc, aenc_out_frame.buffer, aenc_out_frame.encoded_bytes); 131 | 132 | bytes += aenc_out_frame.encoded_bytes; 133 | if (bytes > 50000) { 134 | curr_time = get_timestamp(); 135 | ESP_LOGI(TAG, "audio bitrate: %.1f bps", 1000.0 * (bytes * 8.0 / (float)(curr_time - last_time))); 136 | last_time = curr_time; 137 | bytes = 0; 138 | } 139 | } 140 | } 141 | vTaskDelay(pdMS_TO_TICKS(5)); 142 | 143 | } else { 144 | vTaskDelay(pdMS_TO_TICKS(100)); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /examples/esp32/main/camera.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "esp_log.h" 6 | #include "freertos/FreeRTOS.h" 7 | #include "freertos/task.h" 8 | 9 | #include "esp_camera.h" 10 | #include "esp_timer.h" 11 | 12 | #include "peer_connection.h" 13 | 14 | extern PeerConnection* g_pc; 15 | extern int gDataChannelOpened; 16 | extern PeerConnectionState eState; 17 | extern SemaphoreHandle_t xSemaphore; 18 | extern int get_timestamp(); 19 | static const char* TAG = "Camera"; 20 | 21 | #if defined(CONFIG_ESP32S3_XIAO_SENSE) 22 | #define CAM_PIN_PWDN -1 23 | #define CAM_PIN_RESET -1 24 | #define CAM_PIN_XCLK 10 25 | #define CAM_PIN_SIOD 40 26 | #define CAM_PIN_SIOC 39 27 | #define CAM_PIN_D7 48 28 | #define CAM_PIN_D6 11 29 | #define CAM_PIN_D5 12 30 | #define CAM_PIN_D4 14 31 | #define CAM_PIN_D3 16 32 | #define CAM_PIN_D2 18 33 | #define CAM_PIN_D1 17 34 | #define CAM_PIN_D0 15 35 | #define CAM_PIN_VSYNC 38 36 | #define CAM_PIN_HREF 47 37 | #define CAM_PIN_PCLK 13 38 | #elif defined(CONFIG_ESP32_M5STACK_CAMERA_B) 39 | #define CAM_PIN_PWDN -1 40 | #define CAM_PIN_RESET 15 41 | #define CAM_PIN_XCLK 27 42 | #define CAM_PIN_SIOD 22 43 | #define CAM_PIN_SIOC 23 44 | #define CAM_PIN_D7 19 45 | #define CAM_PIN_D6 36 46 | #define CAM_PIN_D5 18 47 | #define CAM_PIN_D4 39 48 | #define CAM_PIN_D3 5 49 | #define CAM_PIN_D2 34 50 | #define CAM_PIN_D1 35 51 | #define CAM_PIN_D0 32 52 | #define CAM_PIN_VSYNC 25 53 | #define CAM_PIN_HREF 26 54 | #define CAM_PIN_PCLK 21 55 | #elif defined(CONFIG_ESP32S3_EYE) 56 | #define CAM_PIN_PWDN -1 57 | #define CAM_PIN_RESET -1 58 | #define CAM_PIN_XCLK 15 59 | #define CAM_PIN_SIOD 4 60 | #define CAM_PIN_SIOC 5 61 | #define CAM_PIN_D7 16 62 | #define CAM_PIN_D6 17 63 | #define CAM_PIN_D5 18 64 | #define CAM_PIN_D4 12 65 | #define CAM_PIN_D3 10 66 | #define CAM_PIN_D2 8 67 | #define CAM_PIN_D1 9 68 | #define CAM_PIN_D0 11 69 | #define CAM_PIN_VSYNC 6 70 | #define CAM_PIN_HREF 7 71 | #define CAM_PIN_PCLK 13 72 | #else // ESP32-EYE 73 | #define CAM_PIN_PWDN -1 74 | #define CAM_PIN_RESET -1 75 | #define CAM_PIN_XCLK 4 76 | #define CAM_PIN_SIOD 18 77 | #define CAM_PIN_SIOC 23 78 | #define CAM_PIN_D7 36 79 | #define CAM_PIN_D6 37 80 | #define CAM_PIN_D5 38 81 | #define CAM_PIN_D4 39 82 | #define CAM_PIN_D3 35 83 | #define CAM_PIN_D2 14 84 | #define CAM_PIN_D1 13 85 | #define CAM_PIN_D0 34 86 | #define CAM_PIN_VSYNC 5 87 | #define CAM_PIN_HREF 27 88 | #define CAM_PIN_PCLK 25 89 | #endif 90 | 91 | static camera_config_t camera_config = { 92 | 93 | .ledc_timer = LEDC_TIMER_0, 94 | .ledc_channel = LEDC_CHANNEL_0, 95 | .pin_d7 = CAM_PIN_D7, 96 | .pin_d6 = CAM_PIN_D6, 97 | .pin_d5 = CAM_PIN_D5, 98 | .pin_d4 = CAM_PIN_D4, 99 | .pin_d3 = CAM_PIN_D3, 100 | .pin_d2 = CAM_PIN_D2, 101 | .pin_d1 = CAM_PIN_D1, 102 | .pin_d0 = CAM_PIN_D0, 103 | .pin_xclk = CAM_PIN_XCLK, 104 | .pin_pclk = CAM_PIN_PCLK, 105 | .pin_vsync = CAM_PIN_VSYNC, 106 | .pin_href = CAM_PIN_HREF, 107 | .pin_sccb_sda = CAM_PIN_SIOD, 108 | .pin_sccb_scl = CAM_PIN_SIOC, 109 | .pin_pwdn = CAM_PIN_PWDN, 110 | .pin_reset = CAM_PIN_RESET, 111 | .xclk_freq_hz = 20000000, 112 | .pixel_format = PIXFORMAT_JPEG, 113 | .frame_size = FRAMESIZE_VGA, 114 | .jpeg_quality = 10, 115 | .fb_count = 2, 116 | .grab_mode = CAMERA_GRAB_WHEN_EMPTY}; 117 | 118 | esp_err_t camera_init() { 119 | // initialize the camera 120 | esp_err_t err = esp_camera_init(&camera_config); 121 | if (err != ESP_OK) { 122 | ESP_LOGE(TAG, "Camera Init Failed"); 123 | return err; 124 | } 125 | 126 | return ESP_OK; 127 | } 128 | 129 | void camera_task(void* pvParameters) { 130 | static int fps = 0; 131 | static int64_t last_time; 132 | int64_t curr_time; 133 | 134 | camera_fb_t* fb = NULL; 135 | 136 | ESP_LOGI(TAG, "Camera Task Started"); 137 | 138 | last_time = get_timestamp(); 139 | 140 | for (;;) { 141 | if ((eState == PEER_CONNECTION_COMPLETED) && gDataChannelOpened) { 142 | fb = esp_camera_fb_get(); 143 | 144 | if (!fb) { 145 | ESP_LOGE(TAG, "Camera capture failed"); 146 | } 147 | 148 | // ESP_LOGI(TAG, "Camera captured. size=%zu, timestamp=%llu", fb->len, fb->timestamp); 149 | if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) { 150 | peer_connection_datachannel_send(g_pc, (char*)fb->buf, fb->len); 151 | xSemaphoreGive(xSemaphore); 152 | } 153 | 154 | fps++; 155 | 156 | if ((fps % 100) == 0) { 157 | curr_time = get_timestamp(); 158 | ESP_LOGI(TAG, "Camera FPS=%.2f", 1000.0f / (float)(curr_time - last_time) * 100.0f); 159 | last_time = curr_time; 160 | } 161 | 162 | esp_camera_fb_return(fb); 163 | } 164 | 165 | vTaskDelay(pdMS_TO_TICKS(1000 / 20)); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/esp32/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | sepfy/libpeer: 4 | path: ../../../ 5 | version: ">=0.0.2" 6 | espressif/esp_audio_codec: "^2.0.0" 7 | espressif/esp32-camera: "^2.0.4" 8 | protocol_examples_common: 9 | path: ${IDF_PATH}/examples/common_components/protocol_examples_common 10 | ## Required IDF version 11 | idf: 12 | version: ">=4.1.0" 13 | # # Put list of dependencies here 14 | # # For components maintained by Espressif: 15 | # component: "~1.0.0" 16 | # # For 3rd party components: 17 | # username/component: ">=1.0.0,<2.0.0" 18 | # username2/component2: 19 | # version: "~1.0.0" 20 | # # For transient dependencies `public` flag can be set. 21 | # # `public` flag doesn't have an effect dependencies of the `main` component. 22 | # # All dependencies of `main` are public by default. 23 | # public: true 24 | -------------------------------------------------------------------------------- /examples/esp32/partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 0x6000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 2M, 6 | 7 | -------------------------------------------------------------------------------- /examples/esp32/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # This file was generated using idf.py save-defconfig. It can be edited manually. 2 | # Espressif IoT Development Framework (ESP-IDF) 5.2.2 Project Minimal Configuration 3 | # 4 | CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 5 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 6 | CONFIG_PARTITION_TABLE_CUSTOM=y 7 | CONFIG_EXAMPLE_CONNECT_IPV6=n 8 | CONFIG_ESP_PHY_REDUCE_TX_POWER=y 9 | CONFIG_SPIRAM=y 10 | CONFIG_SPIRAM_MODE_OCT=y 11 | CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y 12 | CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2048 13 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 14 | CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n 15 | CONFIG_ESP_IPC_TASK_STACK_SIZE=2048 16 | CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=32 17 | CONFIG_LWIP_IPV6_AUTOCONFIG=y 18 | CONFIG_LWIP_IPV6_DHCP6=y 19 | CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744 20 | CONFIG_LWIP_TCP_WND_DEFAULT=5744 21 | CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y 22 | CONFIG_MBEDTLS_SSL_PROTO_DTLS=y 23 | CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=8192 24 | -------------------------------------------------------------------------------- /examples/generic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(sample) 2 | 3 | file(GLOB SRCS "*.c") 4 | 5 | include_directories(${CMAKE_SOURCE_DIR}/src) 6 | 7 | add_executable(sample ${SRCS}) 8 | 9 | target_link_libraries(sample peer pthread) 10 | 11 | -------------------------------------------------------------------------------- /examples/generic/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "peer.h" 9 | #include "reader.h" 10 | 11 | int g_interrupted = 0; 12 | PeerConnection* g_pc = NULL; 13 | PeerConnectionState g_state; 14 | 15 | static void onconnectionstatechange(PeerConnectionState state, void* data) { 16 | printf("state is changed: %s\n", peer_connection_state_to_string(state)); 17 | g_state = state; 18 | } 19 | 20 | static void onopen(void* user_data) { 21 | } 22 | 23 | static void onclose(void* user_data) { 24 | } 25 | 26 | static void onmessage(char* msg, size_t len, void* user_data, uint16_t sid) { 27 | printf("on message: %d %.*s", sid, (int)len, msg); 28 | 29 | if (strncmp(msg, "ping", 4) == 0) { 30 | printf(", send pong\n"); 31 | peer_connection_datachannel_send(g_pc, "pong", 4); 32 | } 33 | } 34 | 35 | static void signal_handler(int signal) { 36 | g_interrupted = 1; 37 | } 38 | 39 | static void* peer_singaling_task(void* data) { 40 | while (!g_interrupted) { 41 | peer_signaling_loop(); 42 | usleep(1000); 43 | } 44 | 45 | pthread_exit(NULL); 46 | } 47 | 48 | static void* peer_connection_task(void* data) { 49 | while (!g_interrupted) { 50 | peer_connection_loop(g_pc); 51 | usleep(1000); 52 | } 53 | 54 | pthread_exit(NULL); 55 | } 56 | 57 | static uint64_t get_timestamp() { 58 | struct timeval tv; 59 | gettimeofday(&tv, NULL); 60 | return tv.tv_sec * 1000 + tv.tv_usec / 1000; 61 | } 62 | 63 | void print_usage(const char* prog_name) { 64 | printf("Usage: %s -u [-t ]\n", prog_name); 65 | } 66 | 67 | void parse_arguments(int argc, char* argv[], const char** url, const char** token) { 68 | *token = NULL; 69 | *url = NULL; 70 | 71 | for (int i = 1; i < argc; i++) { 72 | if (strcmp(argv[i], "-u") == 0 && (i + 1) < argc) { 73 | *url = argv[++i]; 74 | } else if (strcmp(argv[i], "-t") == 0 && (i + 1) < argc) { 75 | *token = argv[++i]; 76 | } else { 77 | print_usage(argv[0]); 78 | exit(1); 79 | } 80 | } 81 | 82 | if (*url == NULL) { 83 | print_usage(argv[0]); 84 | exit(1); 85 | } 86 | } 87 | 88 | int main(int argc, char* argv[]) { 89 | uint64_t curr_time, video_time, audio_time; 90 | uint8_t* buf = NULL; 91 | const char* url = NULL; 92 | const char* token = NULL; 93 | int size; 94 | 95 | pthread_t peer_singaling_thread; 96 | pthread_t peer_connection_thread; 97 | 98 | parse_arguments(argc, argv, &url, &token); 99 | 100 | signal(SIGINT, signal_handler); 101 | 102 | PeerConfiguration config = { 103 | .ice_servers = { 104 | {.urls = "stun:stun.l.google.com:19302"}, 105 | }, 106 | .datachannel = DATA_CHANNEL_STRING, 107 | .video_codec = CODEC_H264, 108 | .audio_codec = CODEC_PCMA}; 109 | 110 | printf("=========== Parsed Arguments ===========\n"); 111 | printf(" %-5s : %s\n", "URL", url); 112 | printf(" %-5s : %s\n", "Token", token ? token : ""); 113 | printf("========================================\n"); 114 | 115 | peer_init(); 116 | g_pc = peer_connection_create(&config); 117 | peer_connection_oniceconnectionstatechange(g_pc, onconnectionstatechange); 118 | peer_connection_ondatachannel(g_pc, onmessage, onopen, onclose); 119 | 120 | peer_signaling_connect(url, token, g_pc); 121 | 122 | pthread_create(&peer_connection_thread, NULL, peer_connection_task, NULL); 123 | pthread_create(&peer_singaling_thread, NULL, peer_singaling_task, NULL); 124 | 125 | reader_init(); 126 | 127 | while (!g_interrupted) { 128 | if (g_state == PEER_CONNECTION_COMPLETED) { 129 | curr_time = get_timestamp(); 130 | 131 | // FPS 25 132 | if (curr_time - video_time > 40) { 133 | video_time = curr_time; 134 | if ((buf = reader_get_video_frame(&size)) != NULL) { 135 | peer_connection_send_video(g_pc, buf, size); 136 | // need to free the buffer 137 | free(buf); 138 | buf = NULL; 139 | } 140 | } 141 | 142 | if (curr_time - audio_time > 20) { 143 | if ((buf = reader_get_audio_frame(&size)) != NULL) { 144 | peer_connection_send_audio(g_pc, buf, size); 145 | buf = NULL; 146 | } 147 | audio_time = curr_time; 148 | } 149 | } 150 | usleep(1000); 151 | } 152 | 153 | pthread_join(peer_singaling_thread, NULL); 154 | pthread_join(peer_connection_thread, NULL); 155 | 156 | reader_deinit(); 157 | 158 | peer_signaling_disconnect(); 159 | peer_connection_destroy(g_pc); 160 | peer_deinit(); 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /examples/generic/reader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int g_video_size = 0; 8 | static int g_audio_size = 0; 9 | static uint8_t* g_video_buf = NULL; 10 | static uint8_t* g_audio_buf = NULL; 11 | static uint8_t* g_pps_buf = NULL; 12 | static uint8_t* g_sps_buf = NULL; 13 | static const uint32_t nalu_start_4bytecode = 0x01000000; 14 | static const uint32_t nalu_start_3bytecode = 0x010000; 15 | 16 | typedef enum H264_NALU_TYPE { 17 | NALU_TYPE_SPS = 7, 18 | NALU_TYPE_PPS = 8, 19 | NALU_TYPE_IDR = 5, 20 | NALU_TYPE_NON_IDR = 1, 21 | } H264_NALU_TYPE; 22 | 23 | int reader_init() { 24 | FILE* video_fp = NULL; 25 | FILE* audio_fp = NULL; 26 | char videofile[] = "test.264"; 27 | char audiofile[] = "alaw08m.wav"; 28 | 29 | video_fp = fopen(videofile, "rb"); 30 | 31 | if (video_fp == NULL) { 32 | printf("open file %s failed\n", videofile); 33 | return -1; 34 | } 35 | 36 | fseek(video_fp, 0, SEEK_END); 37 | g_video_size = ftell(video_fp); 38 | fseek(video_fp, 0, SEEK_SET); 39 | g_video_buf = (uint8_t*)calloc(1, g_video_size); 40 | fread(g_video_buf, g_video_size, 1, video_fp); 41 | fclose(video_fp); 42 | 43 | audio_fp = fopen(audiofile, "rb"); 44 | 45 | if (audio_fp == NULL) { 46 | printf("open file %s failed\n", audiofile); 47 | return -1; 48 | } 49 | 50 | fseek(audio_fp, 0, SEEK_END); 51 | g_audio_size = ftell(audio_fp); 52 | fseek(audio_fp, 0, SEEK_SET); 53 | g_audio_buf = (uint8_t*)calloc(1, g_audio_size); 54 | fread(g_audio_buf, 1, g_audio_size, audio_fp); 55 | fclose(audio_fp); 56 | 57 | return 0; 58 | } 59 | 60 | uint8_t* reader_h264_find_nalu(uint8_t* buf_start, uint8_t* buf_end) { 61 | uint8_t* p = buf_start; 62 | 63 | while ((p + 3) < buf_end) { 64 | if (memcmp(p, &nalu_start_4bytecode, 4) == 0) { 65 | return p; 66 | } else if (memcmp(p, &nalu_start_3bytecode, 3) == 0) { 67 | return p; 68 | } 69 | p++; 70 | } 71 | 72 | return buf_end; 73 | } 74 | 75 | uint8_t* reader_get_video_frame(int* size) { 76 | uint8_t* buf = NULL; 77 | static int pps_size = 0; 78 | static int sps_size = 0; 79 | uint8_t* buf_end = g_video_buf + g_video_size; 80 | 81 | static uint8_t* pstart = NULL; 82 | static uint8_t* pend = NULL; 83 | size_t nalu_size; 84 | 85 | if (!pstart) 86 | pstart = g_video_buf; 87 | 88 | pend = reader_h264_find_nalu(pstart + 2, buf_end); 89 | 90 | if (pend == buf_end) { 91 | pstart = NULL; 92 | return NULL; 93 | } 94 | 95 | nalu_size = pend - pstart; 96 | int start_code_offset = memcmp(pstart, &nalu_start_3bytecode, 3) == 0 ? 3 : 4; 97 | H264_NALU_TYPE nalu_type = (H264_NALU_TYPE)(pstart[start_code_offset] & 0x1f); 98 | 99 | switch (nalu_type) { 100 | case NALU_TYPE_SPS: 101 | sps_size = nalu_size; 102 | if (g_sps_buf != NULL) { 103 | free(g_sps_buf); 104 | g_sps_buf = NULL; 105 | } 106 | g_sps_buf = (uint8_t*)calloc(1, sps_size); 107 | memcpy(g_sps_buf, pstart, sps_size); 108 | break; 109 | case NALU_TYPE_PPS: 110 | pps_size = nalu_size; 111 | if (g_pps_buf != NULL) { 112 | free(g_pps_buf); 113 | g_pps_buf = NULL; 114 | } 115 | g_pps_buf = (uint8_t*)calloc(1, pps_size); 116 | memcpy(g_pps_buf, pstart, pps_size); 117 | 118 | break; 119 | case NALU_TYPE_IDR: 120 | *size = sps_size + pps_size + nalu_size; 121 | buf = (uint8_t*)calloc(1, *size); 122 | memcpy(buf, g_sps_buf, sps_size); 123 | memcpy(buf + sps_size, g_pps_buf, pps_size); 124 | memcpy(buf + sps_size + pps_size, pstart, nalu_size); 125 | 126 | break; 127 | case NALU_TYPE_NON_IDR: 128 | default: 129 | *size = nalu_size; 130 | buf = (uint8_t*)calloc(1, *size); 131 | memcpy(buf, pstart, nalu_size); 132 | 133 | break; 134 | } 135 | 136 | pstart = pend; 137 | 138 | return buf; 139 | } 140 | 141 | uint8_t* reader_get_audio_frame(int* size) { 142 | // sample-rate=8000 channels=1 format=S16LE duration=20ms alaw-size=160 143 | uint8_t* buf = NULL; 144 | static int pos = 0; 145 | *size = 160; 146 | if ((pos + *size) > g_audio_size) { 147 | pos = 0; 148 | } 149 | 150 | buf = g_audio_buf + pos; 151 | pos += *size; 152 | 153 | return buf; 154 | } 155 | 156 | void reader_deinit() { 157 | if (g_sps_buf != NULL) { 158 | free(g_sps_buf); 159 | g_sps_buf = NULL; 160 | } 161 | 162 | if (g_pps_buf != NULL) { 163 | free(g_pps_buf); 164 | g_pps_buf = NULL; 165 | } 166 | 167 | if (g_video_buf != NULL) { 168 | free(g_video_buf); 169 | g_video_buf = NULL; 170 | } 171 | 172 | if (g_audio_buf != NULL) { 173 | free(g_audio_buf); 174 | g_audio_buf = NULL; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /examples/generic/reader.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int reader_init(); 7 | 8 | uint8_t* reader_get_video_frame(int* size); 9 | 10 | uint8_t* reader_get_audio_frame(int* size); 11 | 12 | void reader_deinit(); 13 | -------------------------------------------------------------------------------- /examples/pico/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/pico/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(PICO_BOARD pico_w) 4 | 5 | include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake) 6 | 7 | project(pico-peer) 8 | pico_sdk_init() 9 | 10 | include(FreeRTOS_Kernel_import.cmake) 11 | 12 | 13 | include(${CMAKE_CURRENT_LIST_DIR}/../../third_party/coreHTTP/httpFilePaths.cmake) 14 | include(${CMAKE_CURRENT_LIST_DIR}/../../third_party/coreMQTT/mqttFilePaths.cmake) 15 | include_directories( 16 | ${CMAKE_CURRENT_LIST_DIR}/ 17 | ${CMAKE_BINARY_DIR}/ 18 | ${CMAKE_CURRENT_LIST_DIR}/../../third_party/libsrtp/include/ 19 | ${CMAKE_CURRENT_LIST_DIR}/../../third_party/libsrtp/crypto/include/ 20 | ${CMAKE_CURRENT_LIST_DIR}/../../third_party/cJSON/ 21 | ${CMAKE_CURRENT_LIST_DIR}/../../src/ 22 | ${HTTP_INCLUDE_PUBLIC_DIRS} 23 | ${MQTT_INCLUDE_PUBLIC_DIRS} 24 | ) 25 | 26 | set(PICO_SDK_LIBS 27 | pico_stdlib 28 | pico_cyw43_arch_lwip_sys_freertos 29 | pico_mbedtls 30 | FreeRTOS-Kernel-Heap4 31 | ) 32 | 33 | # Build libsrtp 34 | file(GLOB LIBSRTP_SRC 35 | ../../third_party/libsrtp/srtp/srtp.c 36 | ../../third_party/libsrtp/crypto/cipher/cipher.c 37 | ../../third_party/libsrtp/crypto/cipher/null_cipher.c 38 | ../../third_party/libsrtp/crypto/cipher/aes.c 39 | ../../third_party/libsrtp/crypto/cipher/aes_icm.c 40 | ../../third_party/libsrtp/crypto/cipher/cipher_test_cases.c 41 | ../../third_party/libsrtp/crypto/hash/auth.c 42 | ../../third_party/libsrtp/crypto/hash/null_auth.c 43 | ../../third_party/libsrtp/crypto/hash/hmac.c 44 | ../../third_party/libsrtp/crypto/hash/sha1.c 45 | ../../third_party/libsrtp/crypto/hash/auth_test_cases.c 46 | ../../third_party/libsrtp/crypto/kernel/alloc.c 47 | ../../third_party/libsrtp/crypto/kernel/crypto_kernel.c 48 | ../../third_party/libsrtp/crypto/kernel/err.c 49 | ../../third_party/libsrtp/crypto/kernel/key.c 50 | ../../third_party/libsrtp/crypto/math/datatypes.c 51 | ../../third_party/libsrtp/crypto/math/stat.c 52 | ../../third_party/libsrtp/crypto/replay/rdb.c 53 | ../../third_party/libsrtp/crypto/replay/rdbx.c 54 | ../../third_party/libsrtp/crypto/replay/ut_sim.c 55 | ) 56 | 57 | add_library(srtp STATIC 58 | ${LIBSRTP_SRC} 59 | ) 60 | 61 | target_compile_definitions(srtp PRIVATE 62 | HAVE_CONFIG_H 63 | PRIx64=PRIx32 64 | ) 65 | 66 | target_link_libraries(srtp 67 | ${PICO_SDK_LIBS} 68 | ) 69 | 70 | add_custom_command(TARGET srtp 71 | POST_BUILD 72 | COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/srtp2/ 73 | COMMAND ${CMAKE_COMMAND} -E copy 74 | ../../../third_party/libsrtp/include/srtp.h 75 | ${CMAKE_BINARY_DIR}/srtp2/ 76 | ) 77 | 78 | # Build cJSON 79 | add_library(cjson STATIC 80 | ../../third_party/cJSON/cJSON.c 81 | ) 82 | 83 | # Build peer 84 | file(GLOB LIBPEER_SRC "../../src/*.c") 85 | add_library(peer STATIC 86 | ${LIBPEER_SRC} 87 | ${HTTP_SOURCES} 88 | ${MQTT_SOURCES} 89 | ${MQTT_SERIALIZER_SOURCES} 90 | ) 91 | 92 | target_compile_definitions(peer PRIVATE 93 | __BYTE_ORDER=__LITTLE_ENDIAN 94 | CONFIG_USE_LWIP=1 95 | CONFIG_USE_USRSCTP=0 96 | CONFIG_MBEDTLS_2_X=1 97 | CONFIG_DATA_BUFFER_SIZE=512 98 | CONFIG_AUDIO_BUFFER_SIZE=2048 99 | CONFIG_HTTP_BUFFER_SIZE=1024 100 | CONFIG_SDP_BUFFER_SIZE=4096 101 | HTTP_DO_NOT_USE_CUSTOM_CONFIG 102 | MQTT_DO_NOT_USE_CUSTOM_CONFIG 103 | ) 104 | 105 | target_link_libraries(peer 106 | ${PICO_SDK_LIBS} 107 | srtp 108 | cjson 109 | ) 110 | 111 | pico_generate_pio_header(pico_peer ${CMAKE_CURRENT_LIST_DIR}/rp2040_i2s_example/i2s.pio) 112 | 113 | # Build pico_peer 114 | add_executable(pico_peer 115 | main.c 116 | rp2040_i2s_example/i2s.c 117 | pcm-g711/pcm-g711/g711.c 118 | ) 119 | 120 | target_compile_definitions(pico_peer PRIVATE 121 | WIFI_SSID="$ENV{WIFI_SSID}" 122 | WIFI_PASSWORD="$ENV{WIFI_PASSWORD}" 123 | PICO_HEAP_SIZE=0x20000 124 | #PICO_DEBUG_MALLOC=1 125 | ) 126 | 127 | target_link_libraries(pico_peer 128 | ${PICO_SDK_LIBS} 129 | peer 130 | ) 131 | 132 | pico_enable_stdio_usb(pico_peer 1) 133 | pico_enable_stdio_uart(pico_peer 0) 134 | pico_add_extra_outputs(pico_peer) 135 | -------------------------------------------------------------------------------- /examples/pico/FreeRTOSConfig.h: -------------------------------------------------------------------------------- 1 | #ifndef FREERTOS_CONFIG_H 2 | #define FREERTOS_CONFIG_H 3 | 4 | /* Scheduler Related */ 5 | #define configUSE_PREEMPTION 1 6 | #define configUSE_TICKLESS_IDLE 0 7 | #define configUSE_IDLE_HOOK 0 8 | #define configUSE_TICK_HOOK 0 9 | #define configTICK_RATE_HZ ((TickType_t)1000) 10 | #define configMAX_PRIORITIES 32 11 | #define configMINIMAL_STACK_SIZE (configSTACK_DEPTH_TYPE)512 12 | #define configUSE_16_BIT_TICKS 0 13 | 14 | #define configIDLE_SHOULD_YIELD 1 15 | 16 | /* Synchronization Related */ 17 | #define configUSE_MUTEXES 1 18 | #define configUSE_RECURSIVE_MUTEXES 1 19 | #define configUSE_APPLICATION_TASK_TAG 0 20 | #define configUSE_COUNTING_SEMAPHORES 1 21 | #define configQUEUE_REGISTRY_SIZE 8 22 | #define configUSE_QUEUE_SETS 1 23 | #define configUSE_TIME_SLICING 1 24 | #define configUSE_NEWLIB_REENTRANT 0 25 | // todo need this for lwip FreeRTOS sys_arch to compile 26 | #define configENABLE_BACKWARD_COMPATIBILITY 1 27 | #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 28 | 29 | /* System */ 30 | #define configSTACK_DEPTH_TYPE uint32_t 31 | #define configMESSAGE_BUFFER_LENGTH_TYPE size_t 32 | 33 | /* Memory allocation related definitions. */ 34 | #define configSUPPORT_STATIC_ALLOCATION 0 35 | #define configSUPPORT_DYNAMIC_ALLOCATION 1 36 | #define configTOTAL_HEAP_SIZE (48 * 1024) 37 | #define configAPPLICATION_ALLOCATED_HEAP 0 38 | 39 | /* Hook function related definitions. */ 40 | #define configCHECK_FOR_STACK_OVERFLOW 0 41 | #define configUSE_MALLOC_FAILED_HOOK 0 42 | #define configUSE_DAEMON_TASK_STARTUP_HOOK 0 43 | 44 | /* Run time and task stats gathering related definitions. */ 45 | #define configGENERATE_RUN_TIME_STATS 0 46 | #define configUSE_TRACE_FACILITY 1 47 | #define configUSE_STATS_FORMATTING_FUNCTIONS 0 48 | 49 | /* Co-routine related definitions. */ 50 | #define configUSE_CO_ROUTINES 0 51 | #define configMAX_CO_ROUTINE_PRIORITIES 1 52 | 53 | /* Software timer related definitions. */ 54 | #define configUSE_TIMERS 1 55 | #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) 56 | #define configTIMER_QUEUE_LENGTH 10 57 | #define configTIMER_TASK_STACK_DEPTH 1024 58 | 59 | /* Interrupt nesting behaviour configuration. */ 60 | /* 61 | #define configKERNEL_INTERRUPT_PRIORITY [dependent of processor] 62 | #define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application] 63 | #define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application] 64 | */ 65 | 66 | #define configNUMBER_OF_CORES 2 67 | /* SMP (configNUMBER_OF_CORES > 1) only */ 68 | #define configTICK_CORE 0 69 | #define configRUN_MULTIPLE_PRIORITIES 1 70 | #if configNUMBER_OF_CORES > 1 71 | #define configUSE_CORE_AFFINITY 1 72 | #endif 73 | #define configUSE_PASSIVE_IDLE_HOOK 0 74 | 75 | /* Armv8-M */ 76 | 77 | /* Not currently supported */ 78 | #define configENABLE_MPU 0 79 | #define configENABLE_FPU 1 80 | /* Not currently supported */ 81 | #define configENABLE_TRUSTZONE 0 82 | #define configRUN_FREERTOS_SECURE_ONLY 1 83 | // see https://www.freertos.org/RTOS-Cortex-M3-M4.html 84 | #define configMAX_SYSCALL_INTERRUPT_PRIORITY 16 85 | 86 | /* RP2xxx specific */ 87 | #define configSUPPORT_PICO_SYNC_INTEROP 1 88 | #define configSUPPORT_PICO_TIME_INTEROP 1 89 | 90 | #include 91 | /* Define to trap errors during development. */ 92 | #define configASSERT(x) assert(x) 93 | 94 | /* Set the following definitions to 1 to include the API function, or zero 95 | to exclude the API function. */ 96 | #define INCLUDE_vTaskPrioritySet 1 97 | #define INCLUDE_uxTaskPriorityGet 1 98 | #define INCLUDE_vTaskDelete 1 99 | #define INCLUDE_vTaskSuspend 1 100 | #define INCLUDE_vTaskDelayUntil 1 101 | #define INCLUDE_vTaskDelay 1 102 | #define INCLUDE_xTaskGetSchedulerState 1 103 | #define INCLUDE_xTaskGetCurrentTaskHandle 1 104 | #define INCLUDE_uxTaskGetStackHighWaterMark 1 105 | #define INCLUDE_xTaskGetIdleTaskHandle 1 106 | #define INCLUDE_eTaskGetState 1 107 | #define INCLUDE_xTimerPendFunctionCall 1 108 | #define INCLUDE_xTaskAbortDelay 1 109 | #define INCLUDE_xTaskGetHandle 1 110 | #define INCLUDE_xTaskResumeFromISR 1 111 | #define INCLUDE_xQueueGetMutexHolder 1 112 | 113 | #endif // FREERTOS_CONFIG_H 114 | -------------------------------------------------------------------------------- /examples/pico/FreeRTOS_Kernel_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /portable/ThirdParty/GCC/RP2040/FREERTOS_KERNEL_import.cmake 2 | 3 | # This can be dropped into an external project to help locate the FreeRTOS kernel 4 | # It should be include()ed prior to project(). Alternatively this file may 5 | # or the CMakeLists.txt in this directory may be included or added via add_subdirectory 6 | # respectively. 7 | 8 | if (DEFINED ENV{FREERTOS_KERNEL_PATH} AND (NOT FREERTOS_KERNEL_PATH)) 9 | set(FREERTOS_KERNEL_PATH $ENV{FREERTOS_KERNEL_PATH}) 10 | message("Using FREERTOS_KERNEL_PATH from environment ('${FREERTOS_KERNEL_PATH}')") 11 | endif () 12 | 13 | # first pass we look in old tree; second pass we look in new tree 14 | foreach(SEARCH_PASS RANGE 0 1) 15 | if (SEARCH_PASS) 16 | # ports may be moving to submodule in the future 17 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/Community-Supported-Ports/GCC") 18 | set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../../..") 19 | else() 20 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "portable/ThirdParty/GCC") 21 | set(FREERTOS_KERNEL_RP2040_BACK_PATH "../../../..") 22 | endif() 23 | 24 | if(PICO_PLATFORM STREQUAL "rp2040") 25 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2040") 26 | else() 27 | if (PICO_PLATFORM STREQUAL "rp2350-riscv") 28 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_RISC-V") 29 | else() 30 | set(FREERTOS_KERNEL_RP2040_RELATIVE_PATH "${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/RP2350_ARM_NTZ") 31 | endif() 32 | endif() 33 | 34 | if (NOT FREERTOS_KERNEL_PATH) 35 | # check if we are inside the FreeRTOS kernel tree (i.e. this file has been included directly) 36 | get_filename_component(_ACTUAL_PATH ${CMAKE_CURRENT_LIST_DIR} REALPATH) 37 | get_filename_component(_POSSIBLE_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} REALPATH) 38 | if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) 39 | get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) 40 | endif() 41 | if (_ACTUAL_PATH STREQUAL _POSSIBLE_PATH) 42 | get_filename_component(FREERTOS_KERNEL_PATH ${CMAKE_CURRENT_LIST_DIR}/${FREERTOS_KERNEL_RP2040_BACK_PATH} REALPATH) 43 | message("Setting FREERTOS_KERNEL_PATH to ${FREERTOS_KERNEL_PATH} based on location of FreeRTOS-Kernel-import.cmake") 44 | break() 45 | elseif (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../FreeRTOS-Kernel") 46 | set(FREERTOS_KERNEL_PATH ${PICO_SDK_PATH}/../FreeRTOS-Kernel) 47 | message("Defaulting FREERTOS_KERNEL_PATH as sibling of PICO_SDK_PATH: ${FREERTOS_KERNEL_PATH}") 48 | break() 49 | endif() 50 | endif () 51 | 52 | if (NOT FREERTOS_KERNEL_PATH) 53 | foreach(POSSIBLE_SUFFIX Source FreeRTOS-Kernel FreeRTOS/Source) 54 | # check if FreeRTOS-Kernel exists under directory that included us 55 | set(SEARCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}) 56 | get_filename_component(_POSSIBLE_PATH ${SEARCH_ROOT}/${POSSIBLE_SUFFIX} REALPATH) 57 | if (EXISTS ${_POSSIBLE_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) 58 | get_filename_component(FREERTOS_KERNEL_PATH ${_POSSIBLE_PATH} REALPATH) 59 | message("Setting FREERTOS_KERNEL_PATH to '${FREERTOS_KERNEL_PATH}' found relative to enclosing project") 60 | break() 61 | endif() 62 | endforeach() 63 | if (FREERTOS_KERNEL_PATH) 64 | break() 65 | endif() 66 | endif() 67 | 68 | # user must have specified 69 | if (FREERTOS_KERNEL_PATH) 70 | if (EXISTS "${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") 71 | break() 72 | endif() 73 | endif() 74 | endforeach () 75 | 76 | if (NOT FREERTOS_KERNEL_PATH) 77 | message(FATAL_ERROR "FreeRTOS location was not specified. Please set FREERTOS_KERNEL_PATH.") 78 | endif() 79 | 80 | set(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" CACHE PATH "Path to the FreeRTOS Kernel") 81 | 82 | get_filename_component(FREERTOS_KERNEL_PATH "${FREERTOS_KERNEL_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 83 | if (NOT EXISTS ${FREERTOS_KERNEL_PATH}) 84 | message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' not found") 85 | endif() 86 | if (NOT EXISTS ${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}/CMakeLists.txt) 87 | message(FATAL_ERROR "Directory '${FREERTOS_KERNEL_PATH}' does not contain a '${PICO_PLATFORM}' port here: ${FREERTOS_KERNEL_RP2040_RELATIVE_PATH}") 88 | endif() 89 | set(FREERTOS_KERNEL_PATH ${FREERTOS_KERNEL_PATH} CACHE PATH "Path to the FreeRTOS_KERNEL" FORCE) 90 | 91 | add_subdirectory(${FREERTOS_KERNEL_PATH}/${FREERTOS_KERNEL_RP2040_RELATIVE_PATH} FREERTOS_KERNEL) 92 | -------------------------------------------------------------------------------- /examples/pico/README.md: -------------------------------------------------------------------------------- 1 | # RP2040 PICO W 2 | Establish a DataChannel connection between PICO and the web 3 | 4 | ## Supported Devices 5 | | Device |Image| 6 | |---|---| 7 | | [PICO W](https://www.raspberrypi.com/products/raspberry-pi-pico/) || 8 | 9 | ## Setup Instructions 10 | ### Download Required Libraries 11 | ``` 12 | $ git clone --recursive https://github.com/sepfy/libpeer 13 | $ git clone --recurisve https://github.com/raspberrypi/pico-sdk 14 | $ git clone --recursive https://github.com/FreeRTOS/FreeRTOS-Kernel 15 | ``` 16 | ### Apply a patch 17 | Modify `libpeer/third_party/libsrtp/include/srtp.h` 18 | 19 | ```patch 20 | @@ -614,7 +614,7 @@ srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy); 21 | * - [other] otherwise. 22 | * 23 | */ 24 | -srtp_err_status_t srtp_remove_stream(srtp_t session, unsigned int ssrc); 25 | +srtp_err_status_t srtp_remove_stream(srtp_t session, uint32_t ssrc); 26 | 27 | /** 28 | * @brief srtp_update() updates all streams in the session. 29 | ``` 30 | 31 | ### Configure Your Build 32 | ``` 33 | $ export PICO_SDK_PATH=/pico-sdk 34 | $ export FREERTOS_KERNEL_PATH=/FreeRTOS-Kernel 35 | $ export WIFI_SSID= 36 | $ export WIFI_PASSWORD= 37 | ``` 38 | 39 | ### Build the Project 40 | ``` 41 | $ cd libpeer/examples/pico 42 | $ mkdir build 43 | $ cd build 44 | $ cmake .. 45 | ``` 46 | 47 | ### Flash and Test 48 | ``` 49 | $ sudo picotool load -f pico_peer.uf2 50 | ``` 51 | -------------------------------------------------------------------------------- /examples/pico/config.h: -------------------------------------------------------------------------------- 1 | /* clang-format off */ 2 | 3 | /* Define to the full name and version of this package. */ 4 | #define PACKAGE_VERSION "2.3.0" 5 | 6 | /* Define to the version of this package. */ 7 | #define PACKAGE_STRING "pico 2.3.0" 8 | 9 | /* Define to enabled debug logging for all mudules. */ 10 | /* #undef ENABLE_DEBUG_LOGGING */ 11 | 12 | /* Logging statments will be writen to this file. */ 13 | /* #undef ERR_REPORTING_FILE */ 14 | 15 | /* Define to redirect logging to stdout. */ 16 | /* #undef ERR_REPORTING_STDOUT */ 17 | 18 | /* Define this to use OpenSSL crypto. */ 19 | /* #undef OPENSSL */ 20 | 21 | /* Define this to use AES-GCM. */ 22 | /* #undef GCM */ 23 | 24 | /* Define if building for a CISC machine (e.g. Intel). */ 25 | #define CPU_CISC 1 26 | 27 | /* Define if building for a RISC machine (assume slow byte access). */ 28 | /* #undef CPU_RISC */ 29 | 30 | /* Define to use X86 inlined assembly code */ 31 | /* #undef HAVE_X86 */ 32 | 33 | /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most 34 | significant byte first (like Motorola and SPARC, unlike Intel). */ 35 | /* #undef WORDS_BIGENDIAN */ 36 | 37 | /* Define to 1 if you have the header file. */ 38 | #define HAVE_ARPA_INET_H 1 39 | 40 | /* Define to 1 if you have the header file. */ 41 | /* #undef HAVE_BYTESWAP_H */ 42 | 43 | /* Define to 1 if you have the header file. */ 44 | #define HAVE_INTTYPES_H 1 45 | 46 | /* Define to 1 if you have the header file. */ 47 | #define HAVE_MACHINE_TYPES_H 1 48 | 49 | /* Define to 1 if you have the header file. */ 50 | #define HAVE_NETINET_IN_H 1 51 | 52 | /* Define to 1 if you have the header file. */ 53 | #define HAVE_STDINT_H 1 54 | 55 | /* Define to 1 if you have the header file. */ 56 | #define HAVE_STDLIB_H 1 57 | 58 | /* Define to 1 if you have the header file. */ 59 | /* #undef HAVE_SYS_INT_TYPES_H */ 60 | 61 | /* Define to 1 if you have the header file. */ 62 | #define HAVE_SYS_SOCKET_H 1 63 | 64 | /* Define to 1 if you have the header file. */ 65 | #define HAVE_SYS_TYPES_H 1 66 | 67 | /* Define to 1 if you have the header file. */ 68 | #define HAVE_UNISTD_H 1 69 | 70 | /* Define to 1 if you have the header file. */ 71 | /* #undef HAVE_WINDOWS_H */ 72 | 73 | /* Define to 1 if you have the header file. */ 74 | /* #undef HAVE_WINSOCK2_H */ 75 | 76 | /* Define to 1 if you have the `inet_aton' function. */ 77 | #define HAVE_INET_ATON 1 78 | 79 | /* Define to 1 if you have the `sigaction' function. */ 80 | /* #undef HAVE_SIGACTION */ 81 | 82 | /* Define to 1 if you have the `usleep' function. */ 83 | #define HAVE_USLEEP 1 84 | 85 | /* Define to 1 if the system has the type `uint8_t'. */ 86 | #define HAVE_UINT8_T 1 87 | 88 | /* Define to 1 if the system has the type `uint16_t'. */ 89 | #define HAVE_UINT16_T 1 90 | 91 | /* Define to 1 if the system has the type `uint32_t'. */ 92 | #define HAVE_UINT32_T 1 93 | 94 | /* Define to 1 if the system has the type `uint64_t'. */ 95 | #define HAVE_UINT64_T 1 96 | 97 | /* Define to 1 if the system has the type `int32_t'. */ 98 | #define HAVE_INT32_T 1 99 | 100 | /* The size of `unsigned long', as computed by sizeof. */ 101 | #define SIZEOF_UNSIGNED_LONG 4 102 | 103 | /* The size of `unsigned long long', as computed by sizeof. */ 104 | #define SIZEOF_UNSIGNED_LONG_LONG 8 105 | 106 | /* Define inline to what is supported by compiler */ 107 | #define HAVE_INLINE 1 108 | /* #undef HAVE___INLINE */ 109 | #ifndef HAVE_INLINE 110 | #ifdef HAVE___INLINE 111 | #define inline __inline 112 | #else 113 | #define inline 114 | #endif 115 | #endif 116 | -------------------------------------------------------------------------------- /examples/pico/lwipopts.h: -------------------------------------------------------------------------------- 1 | #ifndef LWIPOPTS_H 2 | #define LWIPOPTS_H 3 | 4 | #define NO_SYS 0 5 | #define LWIP_SOCKET 1 6 | #if PICO_CYW43_ARCH_POLL 7 | #define MEM_LIBC_MALLOC 1 8 | #else 9 | // MEM_LIBC_MALLOC is incompatible with non polling versions 10 | #define MEM_LIBC_MALLOC 0 11 | #endif 12 | #define MEM_ALIGNMENT 4 13 | #define MEM_SIZE 4000 14 | #define MEMP_NUM_TCP_SEG 32 15 | #define MEMP_NUM_ARP_QUEUE 10 16 | #define PBUF_POOL_SIZE 24 17 | #define LWIP_ARP 1 18 | #define LWIP_ETHERNET 1 19 | #define LWIP_ICMP 1 20 | #define LWIP_RAW 1 21 | #define TCP_WND (4 * TCP_MSS) 22 | #define TCP_MSS 1460 23 | #define TCP_SND_BUF (4 * TCP_MSS) 24 | #define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) 25 | #define LWIP_NETIF_STATUS_CALLBACK 1 26 | #define LWIP_NETIF_LINK_CALLBACK 1 27 | #define LWIP_NETIF_HOSTNAME 1 28 | #define LWIP_NETCONN 0 29 | #define MEM_STATS 0 30 | #define SYS_STATS 0 31 | #define MEMP_STATS 0 32 | #define LINK_STATS 0 33 | #define LWIP_CHKSUM_ALGORITHM 3 34 | #define LWIP_DHCP 1 35 | #define LWIP_IPV4 1 36 | #define LWIP_IPV6 1 37 | #define LWIP_IGMP 1 38 | #define LWIP_TCP 1 39 | #define LWIP_UDP 1 40 | #define LWIP_DNS 1 41 | #define LWIP_TCP_KEEPALIVE 1 42 | #define LWIP_NETIF_TX_SINGLE_PBUF 1 43 | #define DHCP_DOES_ARP_CHECK 0 44 | #define LWIP_DHCP_DOES_ACD_CHECK 0 45 | 46 | #if !NO_SYS 47 | #define TCPIP_THREAD_STACKSIZE 1024 48 | #define DEFAULT_THREAD_STACKSIZE 1024 49 | #define DEFAULT_RAW_RECVMBOX_SIZE 8 50 | #define DEFAULT_UDP_RECVMBOX_SIZE 8 51 | #define DEFAULT_TCP_RECVMBOX_SIZE 8 52 | #define TCPIP_MBOX_SIZE 8 53 | #define LWIP_TIMEVAL_PRIVATE 0 54 | 55 | // not necessary, can be done either way 56 | #define LWIP_TCPIP_CORE_LOCKING_INPUT 1 57 | 58 | #define LWIP_SO_RCVTIMEO 1 59 | #endif 60 | 61 | #endif // LWIPOPTS_H 62 | -------------------------------------------------------------------------------- /examples/pico/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | #include "FreeRTOS.h" 8 | #include "hardware/clocks.h" 9 | #include "pico/cyw43_arch.h" 10 | #include "pico/multicore.h" 11 | #include "pico/stdlib.h" 12 | #include "task.h" 13 | 14 | #include "hardware/dma.h" 15 | 16 | #include "pcm-g711/pcm-g711/g711.h" 17 | #include "peer.h" 18 | #include "rp2040_i2s_example/i2s.h" 19 | 20 | static __attribute__((aligned(8))) pio_i2s i2s; 21 | 22 | #define TEST_TASK_PRIORITY (tskIDLE_PRIORITY + 1UL) 23 | 24 | TaskHandle_t xPcTaskHandle; 25 | PeerConnection* g_pc; 26 | PeerConnectionState eState = PEER_CONNECTION_CLOSED; 27 | int gDataChannelOpened = 0; 28 | 29 | static void oniceconnectionstatechange(PeerConnectionState state, void* user_data) { 30 | eState = state; 31 | printf("state = %d\n", state); 32 | } 33 | 34 | static void onmessage(char* msg, size_t len, void* userdata, uint16_t sid) { 35 | if (strncmp(msg, "ping", 4) == 0) { 36 | printf("Got ping, send pong\n"); 37 | peer_connection_datachannel_send(g_pc, "pong", 4); 38 | } 39 | } 40 | 41 | void onopen(void* userdata) { 42 | gDataChannelOpened = 1; 43 | } 44 | 45 | static void onclose(void* userdata) { 46 | } 47 | #if 1 48 | uint32_t get_epoch_time() { 49 | struct timeval tv; 50 | gettimeofday(&tv, NULL); 51 | return (uint32_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; 52 | } 53 | #endif 54 | static void dma_i2s_in_handler(void) { 55 | int8_t alaw[AUDIO_BUFFER_FRAMES]; 56 | int16_t pcm[AUDIO_BUFFER_FRAMES]; 57 | int32_t* input_buffer; 58 | if (*(int32_t**)dma_hw->ch[i2s.dma_ch_in_ctrl].read_addr == i2s.input_buffer) { 59 | input_buffer = i2s.input_buffer; 60 | } else { 61 | input_buffer = i2s.input_buffer + STEREO_BUFFER_SIZE; 62 | } 63 | for (int i = 0; i < AUDIO_BUFFER_FRAMES; i++) { 64 | pcm[i] = (int16_t)(input_buffer[2 * i + 1] >> 16); 65 | alaw[i] = ALaw_Encode(pcm[i]); 66 | } 67 | 68 | #if 1 69 | static uint32_t total_bytes = 0; 70 | static uint32_t last_time = 0; 71 | total_bytes += AUDIO_BUFFER_FRAMES; 72 | uint32_t current_time = get_epoch_time(); 73 | if (current_time - last_time > 1000) { 74 | printf("AUDIO_BUFFER_FRAMES: %d, bps: %d\n", AUDIO_BUFFER_FRAMES, 1000 * total_bytes * 8 / (current_time - last_time)); 75 | total_bytes = 0; 76 | last_time = current_time; 77 | } 78 | #endif 79 | 80 | if (eState == PEER_CONNECTION_COMPLETED) { 81 | peer_connection_send_audio(g_pc, alaw, AUDIO_BUFFER_FRAMES); 82 | } 83 | 84 | dma_hw->ints0 = 1u << i2s.dma_ch_in_data; // clear the IRQ 85 | } 86 | 87 | void peer_connection_task() { 88 | printf("Run peer connection task on the core: %d\n", portGET_CORE_ID()); 89 | while (1) { 90 | peer_connection_loop(g_pc); 91 | vTaskDelay(pdMS_TO_TICKS(1)); 92 | } 93 | } 94 | 95 | void main_task(__unused void* params) { 96 | if (cyw43_arch_init()) { 97 | printf("failed to initialise\n"); 98 | vTaskDelete(NULL); 99 | } 100 | cyw43_arch_enable_sta_mode(); 101 | printf("Connecting to Wi-Fi...\n"); 102 | 103 | while (1) { 104 | if (cyw43_arch_wifi_connect_timeout_ms(WIFI_SSID, WIFI_PASSWORD, CYW43_AUTH_WPA2_AES_PSK, 100000)) { 105 | printf("failed to connect.\n"); 106 | vTaskDelay(1000); 107 | } else { 108 | printf("Connected.\n"); 109 | break; 110 | } 111 | } 112 | 113 | PeerConfiguration config = { 114 | .ice_servers = { 115 | {.urls = "stun:stun.l.google.com:19302"}}, 116 | .audio_codec = CODEC_PCMA, 117 | .datachannel = DATA_CHANNEL_STRING, 118 | }; 119 | 120 | peer_init(); 121 | g_pc = peer_connection_create(&config); 122 | peer_connection_oniceconnectionstatechange(g_pc, oniceconnectionstatechange); 123 | peer_connection_ondatachannel(g_pc, onmessage, onopen, onclose); 124 | 125 | ServiceConfiguration service_config = SERVICE_CONFIG_DEFAULT(); 126 | service_config.client_id = "mypico"; 127 | service_config.pc = g_pc; 128 | peer_signaling_set_config(&service_config); 129 | peer_signaling_join_channel(); 130 | 131 | xTaskCreate(peer_connection_task, "PeerConnectionTask", 4096, NULL, TEST_TASK_PRIORITY, &xPcTaskHandle); 132 | 133 | i2s_program_start_synched(pio0, &i2s_config_default, dma_i2s_in_handler, &i2s); 134 | 135 | printf("Run main task on the core: %d\n", portGET_CORE_ID()); 136 | printf("open https://sepfy.github.io/webrtc?deviceId=mypico\n"); 137 | while (true) { 138 | peer_signaling_loop(); 139 | vTaskDelay(pdMS_TO_TICKS(10)); 140 | } 141 | 142 | cyw43_arch_deinit(); 143 | } 144 | 145 | void vLaunch(void) { 146 | TaskHandle_t task; 147 | xTaskCreate(main_task, "TestMainThread", 4096, NULL, TEST_TASK_PRIORITY, &task); 148 | vTaskCoreAffinitySet(task, 1); 149 | vTaskStartScheduler(); 150 | } 151 | 152 | int main(void) { 153 | stdio_init_all(); 154 | // set_sys_clock_khz(132000, true); 155 | vLaunch(); 156 | return 0; 157 | } 158 | -------------------------------------------------------------------------------- /examples/pico/mbedtls_config.h: -------------------------------------------------------------------------------- 1 | #ifndef MBEDTLS_CONFIG_H 2 | #define MBEDTLS_CONFIG_H 3 | 4 | /* Workaround for some mbedtls source files using INT_MAX without including limits.h */ 5 | #include 6 | 7 | #define MBEDTLS_NO_PLATFORM_ENTROPY 8 | #define MBEDTLS_ENTROPY_HARDWARE_ALT 9 | 10 | #define MBEDTLS_ALLOW_PRIVATE_ACCESS 11 | #define MBEDTLS_HAVE_TIME 12 | 13 | #define MBEDTLS_CIPHER_MODE_CBC 14 | #define MBEDTLS_ECP_DP_SECP192R1_ENABLED 15 | #define MBEDTLS_ECP_DP_SECP224R1_ENABLED 16 | #define MBEDTLS_ECP_DP_SECP256R1_ENABLED 17 | #define MBEDTLS_ECP_DP_SECP384R1_ENABLED 18 | #define MBEDTLS_ECP_DP_SECP521R1_ENABLED 19 | #define MBEDTLS_ECP_DP_SECP192K1_ENABLED 20 | #define MBEDTLS_ECP_DP_SECP224K1_ENABLED 21 | #define MBEDTLS_ECP_DP_SECP256K1_ENABLED 22 | #define MBEDTLS_ECP_DP_BP256R1_ENABLED 23 | #define MBEDTLS_ECP_DP_BP384R1_ENABLED 24 | #define MBEDTLS_ECP_DP_BP512R1_ENABLED 25 | #define MBEDTLS_ECP_DP_CURVE25519_ENABLED 26 | #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED 27 | #define MBEDTLS_PKCS1_V15 28 | #define MBEDTLS_PKCS1_V21 29 | #define MBEDTLS_SHA256_SMALLER 30 | #define MBEDTLS_SSL_SERVER_NAME_INDICATION 31 | #define MBEDTLS_AES_C 32 | #define MBEDTLS_ASN1_PARSE_C 33 | #define MBEDTLS_BIGNUM_C 34 | #define MBEDTLS_CIPHER_C 35 | #define MBEDTLS_CTR_DRBG_C 36 | #define MBEDTLS_ENTROPY_C 37 | #define MBEDTLS_ERROR_C 38 | #define MBEDTLS_MD_C 39 | #define MBEDTLS_MD5_C 40 | #define MBEDTLS_OID_C 41 | #define MBEDTLS_PKCS5_C 42 | #define MBEDTLS_PK_C 43 | #define MBEDTLS_PK_PARSE_C 44 | #define MBEDTLS_PLATFORM_C 45 | #define MBEDTLS_RSA_C 46 | #define MBEDTLS_SHA1_C 47 | #define MBEDTLS_SHA224_C 48 | #define MBEDTLS_SHA256_C 49 | #define MBEDTLS_SSL_CLI_C 50 | #define MBEDTLS_SSL_SRV_C 51 | #define MBEDTLS_SSL_TLS_C 52 | #define MBEDTLS_X509_CRT_PARSE_C 53 | #define MBEDTLS_X509_USE_C 54 | #define MBEDTLS_AES_FEWER_TABLES 55 | 56 | /* TLS 1.2 */ 57 | #define MBEDTLS_SSL_PROTO_TLS1_2 58 | 59 | #define MBEDTLS_DHM_C 60 | #define MBEDTLS_GCM_C 61 | #define MBEDTLS_ECDH_C 62 | #define MBEDTLS_ECP_C 63 | #define MBEDTLS_ECDSA_C 64 | #define MBEDTLS_ASN1_WRITE_C 65 | 66 | // The following is needed to parse a certificate 67 | #define MBEDTLS_PEM_PARSE_C 68 | #define MBEDTLS_BASE64_C 69 | 70 | #define MBEDTLS_ENTROPY_SHA256_ACCUMULATOR 71 | #define MBEDTLS_SSL_DTLS_SRTP 72 | #define MBEDTLS_SSL_EXPORT_KEYS 73 | 74 | #define MBEDTLS_DEBUG_C 75 | #define MBEDTLS_SSL_DTLS_HELLO_VERIFY 76 | #define MBEDTLS_PEM_WRITE_C 77 | #define MBEDTLS_X509_CRT_WRITE_C 78 | #define MBEDTLS_SSL_PROTO_DTLS 79 | #define MBEDTLS_X509_CREATE_C 80 | #define MBEDTLS_GENPRIME 81 | #define MBEDTLS_SSL_COOKIE_C 82 | #define MBEDTLS_PK_WRITE_C 83 | #define MBEDTLS_TIMING_C 84 | #define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE 85 | #define __unix__ 86 | 87 | #endif // MBEDTLS_CONFIG_H 88 | -------------------------------------------------------------------------------- /examples/pico/netinet/in.h: -------------------------------------------------------------------------------- 1 | #ifndef NETINET_INET_H 2 | #define NETINET_INET_H 3 | 4 | #include "lwip/sockets.h" 5 | #endif // NETINET_INET_H 6 | -------------------------------------------------------------------------------- /examples/raspberrypi/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /examples/raspberrypi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(raspberrypi) 4 | 5 | file(GLOB SRCS "*.c") 6 | 7 | find_package(PkgConfig) 8 | 9 | pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4 gstreamer-base-1.0>=1.4) 10 | 11 | include(ExternalProject) 12 | 13 | ExternalProject_Add(libpeer 14 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../ 15 | CMAKE_ARGS 16 | -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/dist 17 | ) 18 | include_directories(${CMAKE_BINARY_DIR}/dist/include ${GST_INCLUDE_DIRS}) 19 | 20 | link_directories(${CMAKE_BINARY_DIR}/dist/lib) 21 | 22 | add_executable(raspberrypi ${SRCS}) 23 | 24 | target_link_libraries(raspberrypi peer pthread ${GST_LIBRARIES}) 25 | 26 | -------------------------------------------------------------------------------- /examples/raspberrypi/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi 2 | 3 | Build a home camera with Raspberry Pi. Support camera video streaming and two-way audio communication. 4 | 5 | ## Hardware 6 | 7 | * [Raspberry Pi 4B](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/) 8 | * [Camera Module](https://www.raspberrypi.com/products/camera-module-v2/) 9 | * [ReSpeaker 2-Mics Pi HAT](https://wiki.seeedstudio.com/ReSpeaker_2_Mics_Pi_HAT_Raspberry/) 10 | * Speaker 11 | 12 | ## Instructions 13 | ### Install 14 | * Install [Raspberry Pi OS Lite Image](https://www.raspberrypi.com/software/operating-systems/) 15 | * Install dependencies 16 | ```bash 17 | $ sudo apt update 18 | $ sudo apt install -y gstreamer1.0-libcamera gstreamer1.0-alsa gstreamer1.0-tools gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good libgstreamer1.0-dev git cmake 19 | ``` 20 | 21 | ### Build 22 | ```bash 23 | $ git clone --recursive https://github.com/sepfy/libpeer 24 | $ cd libpeer/examples/raspberrypi 25 | $ mkdir build && cd build 26 | $ cmake .. 27 | $ make 28 | ``` 29 | 30 | ### Test 31 | - Copy URL from the test [website](https://sepfy.github.io/libpeer) 32 | ```bash 33 | $ ./raspberrypi -u 34 | ``` 35 | - Click Connect button on the website 36 | -------------------------------------------------------------------------------- /examples/raspberrypi/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "peer.h" 16 | 17 | const char CAMERA_PIPELINE[] = "libcamerasrc ! video/x-raw, format=(string)NV12, width=(int)1280, height=(int)960, framerate=(fraction)30/1, interlace-mode=(string)progressive, colorimetry=(string)bt709 ! v4l2h264enc capture-io-mode=4 output-io-mode=4 ! video/x-h264, stream-format=(string)byte-stream, level=(string)4, alighnment=(string)au ! h264parse config-interval=-1 ! appsink name=camera-sink"; 18 | 19 | const char MIC_PIPELINE[] = "alsasrc latency-time=20000 device=plughw:seeed2micvoicec,0 ! audio/x-raw,format=S16LE,rate=8000,channels=1 ! alawenc ! appsink name=mic-sink"; 20 | 21 | const char SPK_PIPELINE[] = "appsrc name=spk-src format=time ! alawdec ! audio/x-raw,format=S16LE,rate=8000,channels=1 ! alsasink sync=false device=plughw:seeed2micvoicec,0"; 22 | 23 | int g_interrupted = 0; 24 | PeerConnection* g_pc = NULL; 25 | PeerConnectionState g_state; 26 | 27 | typedef struct Media { 28 | // Camera elements 29 | GstElement* camera_pipeline; 30 | GstElement* camera_sink; 31 | 32 | // Microphone elements 33 | GstElement* mic_pipeline; 34 | GstElement* mic_sink; 35 | 36 | // Speaker elements 37 | GstElement* spk_pipeline; 38 | GstElement* spk_src; 39 | 40 | } Media; 41 | 42 | Media g_media; 43 | 44 | static void onconnectionstatechange(PeerConnectionState state, void* data) { 45 | printf("state is changed: %d\n", state); 46 | g_state = state; 47 | if (g_state == PEER_CONNECTION_COMPLETED) { 48 | gst_element_set_state(g_media.camera_pipeline, GST_STATE_PLAYING); 49 | gst_element_set_state(g_media.mic_pipeline, GST_STATE_PLAYING); 50 | gst_element_set_state(g_media.spk_pipeline, GST_STATE_PLAYING); 51 | } 52 | } 53 | 54 | static GstFlowReturn on_video_data(GstElement* sink, void* data) { 55 | GstSample* sample; 56 | GstBuffer* buffer; 57 | GstMapInfo info; 58 | 59 | g_signal_emit_by_name(sink, "pull-sample", &sample); 60 | 61 | if (sample) { 62 | buffer = gst_sample_get_buffer(sample); 63 | gst_buffer_map(buffer, &info, GST_MAP_READ); 64 | peer_connection_send_video(g_pc, info.data, info.size); 65 | 66 | gst_buffer_unmap(buffer, &info); 67 | gst_sample_unref(sample); 68 | 69 | return GST_FLOW_OK; 70 | } 71 | 72 | return GST_FLOW_ERROR; 73 | } 74 | 75 | static GstFlowReturn on_audio_data(GstElement* sink, void* data) { 76 | GstSample* sample; 77 | GstBuffer* buffer; 78 | GstMapInfo info; 79 | 80 | g_signal_emit_by_name(sink, "pull-sample", &sample); 81 | 82 | if (sample) { 83 | buffer = gst_sample_get_buffer(sample); 84 | gst_buffer_map(buffer, &info, GST_MAP_READ); 85 | peer_connection_send_audio(g_pc, info.data, info.size); 86 | gst_buffer_unmap(buffer, &info); 87 | gst_sample_unref(sample); 88 | 89 | return GST_FLOW_OK; 90 | } 91 | 92 | return GST_FLOW_ERROR; 93 | } 94 | 95 | static void onopen(void* user_data) { 96 | } 97 | 98 | static void onclose(void* user_data) { 99 | } 100 | 101 | static void onmessasge(char* msg, size_t len, void* user_data, uint16_t sid) { 102 | printf("on message: %s", msg); 103 | 104 | if (strncmp(msg, "ping", 4) == 0) { 105 | printf(", send pong\n"); 106 | peer_connection_datachannel_send(g_pc, "pong", 4); 107 | } 108 | } 109 | 110 | static void on_request_keyframe(void* data) { 111 | printf("request keyframe\n"); 112 | } 113 | 114 | static void signal_handler(int signal) { 115 | g_interrupted = 1; 116 | } 117 | 118 | static void* peer_singaling_task(void* data) { 119 | while (!g_interrupted) { 120 | peer_signaling_loop(); 121 | usleep(1000); 122 | } 123 | 124 | pthread_exit(NULL); 125 | } 126 | 127 | static void* peer_connection_task(void* data) { 128 | while (!g_interrupted) { 129 | peer_connection_loop(g_pc); 130 | usleep(1000); 131 | } 132 | 133 | pthread_exit(NULL); 134 | } 135 | 136 | void print_usage(const char* prog_name) { 137 | printf("Usage: %s -u [-t ]\n", prog_name); 138 | } 139 | 140 | void parse_arguments(int argc, char* argv[], const char** url, const char** token) { 141 | *token = NULL; 142 | *url = NULL; 143 | 144 | for (int i = 1; i < argc; i++) { 145 | if (strcmp(argv[i], "-u") == 0 && (i + 1) < argc) { 146 | *url = argv[++i]; 147 | } else if (strcmp(argv[i], "-t") == 0 && (i + 1) < argc) { 148 | *token = argv[++i]; 149 | } else { 150 | print_usage(argv[0]); 151 | exit(1); 152 | } 153 | } 154 | 155 | if (*url == NULL) { 156 | print_usage(argv[0]); 157 | exit(1); 158 | } 159 | } 160 | 161 | int main(int argc, char* argv[]) { 162 | const char* url = NULL; 163 | const char* token = NULL; 164 | 165 | parse_arguments(argc, argv, &url, &token); 166 | 167 | printf("=========== Parsed Arguments ===========\n"); 168 | printf(" %-5s : %s\n", "URL", url); 169 | printf(" %-5s : %s\n", "Token", token ? token : ""); 170 | printf("========================================\n"); 171 | 172 | pthread_t peer_singaling_thread; 173 | pthread_t peer_connection_thread; 174 | 175 | signal(SIGINT, signal_handler); 176 | 177 | PeerConfiguration config = { 178 | .ice_servers = { 179 | {.urls = "stun:stun.l.google.com:19302"}, 180 | }, 181 | .datachannel = DATA_CHANNEL_STRING, 182 | .video_codec = CODEC_H264, 183 | .audio_codec = CODEC_PCMA, 184 | .on_request_keyframe = on_request_keyframe}; 185 | 186 | gst_init(&argc, &argv); 187 | 188 | g_media.camera_pipeline = gst_parse_launch(CAMERA_PIPELINE, NULL); 189 | g_media.camera_sink = gst_bin_get_by_name(GST_BIN(g_media.camera_pipeline), "camera-sink"); 190 | g_signal_connect(g_media.camera_sink, "new-sample", G_CALLBACK(on_video_data), NULL); 191 | g_object_set(g_media.camera_sink, "emit-signals", TRUE, NULL); 192 | 193 | g_media.mic_pipeline = gst_parse_launch(MIC_PIPELINE, NULL); 194 | g_media.mic_sink = gst_bin_get_by_name(GST_BIN(g_media.mic_pipeline), "mic-sink"); 195 | g_signal_connect(g_media.mic_sink, "new-sample", G_CALLBACK(on_audio_data), NULL); 196 | g_object_set(g_media.mic_sink, "emit-signals", TRUE, NULL); 197 | 198 | g_media.spk_pipeline = gst_parse_launch(SPK_PIPELINE, NULL); 199 | g_media.spk_src = gst_bin_get_by_name(GST_BIN(g_media.spk_pipeline), "spk-src"); 200 | g_object_set(g_media.spk_src, "emit-signals", TRUE, NULL); 201 | 202 | peer_init(); 203 | 204 | g_pc = peer_connection_create(&config); 205 | peer_connection_oniceconnectionstatechange(g_pc, onconnectionstatechange); 206 | peer_connection_ondatachannel(g_pc, onmessasge, onopen, onclose); 207 | 208 | peer_signaling_connect(url, token, g_pc); 209 | 210 | pthread_create(&peer_connection_thread, NULL, peer_connection_task, NULL); 211 | pthread_create(&peer_singaling_thread, NULL, peer_singaling_task, NULL); 212 | 213 | while (!g_interrupted) { 214 | sleep(1); 215 | } 216 | 217 | gst_element_set_state(g_media.camera_pipeline, GST_STATE_NULL); 218 | gst_element_set_state(g_media.mic_pipeline, GST_STATE_NULL); 219 | gst_element_set_state(g_media.spk_pipeline, GST_STATE_NULL); 220 | 221 | pthread_join(peer_singaling_thread, NULL); 222 | pthread_join(peer_connection_thread, NULL); 223 | 224 | peer_signaling_disconnect(); 225 | peer_connection_destroy(g_pc); 226 | peer_deinit(); 227 | 228 | return 0; 229 | } 230 | -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | sepfy/srtp: ^2.0.4 3 | sepfy/usrsctp: ^0.9.5 4 | description: libpeer 5 | license: MIT 6 | url: https://github.com/sepfy/libpeer 7 | version: 0.0.3 8 | -------------------------------------------------------------------------------- /include/peer.h: -------------------------------------------------------------------------------- 1 | ../src/peer.h -------------------------------------------------------------------------------- /include/peer_connection.h: -------------------------------------------------------------------------------- 1 | ../src/peer_connection.h -------------------------------------------------------------------------------- /include/peer_signaling.h: -------------------------------------------------------------------------------- 1 | ../src/peer_signaling.h -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(lib) 2 | 3 | file(GLOB SRCS "*.c") 4 | 5 | file(GLOB HEADERS "peer.h" "peer_connection.h" "peer_signaling.h") 6 | 7 | add_library(peer 8 | ${SRCS} 9 | ${HTTP_SOURCES} 10 | ${MQTT_SOURCES} 11 | ${MQTT_SERIALIZER_SOURCES} 12 | ) 13 | 14 | include_directories(peer PUBLIC 15 | ${HTTP_INCLUDE_PUBLIC_DIRS} 16 | ${MQTT_INCLUDE_PUBLIC_DIRS} 17 | ) 18 | 19 | add_dependencies(peer cjson mbedtls usrsctp srtp2) 20 | 21 | target_link_libraries(peer ${DEP_LIBS}) 22 | 23 | set_target_properties(peer PROPERTIES PUBLIC_HEADER "${HEADERS}") 24 | 25 | install(TARGETS peer 26 | LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/ 27 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_PREFIX}/include/ 28 | ) 29 | 30 | -------------------------------------------------------------------------------- /src/address.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "address.h" 6 | #include "utils.h" 7 | 8 | void addr_set_family(Address* addr, int family) { 9 | switch (family) { 10 | case AF_INET6: 11 | addr->family = AF_INET6; 12 | break; 13 | case AF_INET: 14 | default: 15 | addr->family = AF_INET; 16 | break; 17 | } 18 | } 19 | 20 | void addr_set_port(Address* addr, uint16_t port) { 21 | addr->port = port; 22 | switch (addr->family) { 23 | case AF_INET6: 24 | addr->sin6.sin6_port = htons(port); 25 | break; 26 | case AF_INET: 27 | default: 28 | addr->sin.sin_port = htons(port); 29 | break; 30 | } 31 | } 32 | 33 | int addr_from_string(const char* buf, Address* addr) { 34 | if (inet_pton(AF_INET, buf, &(addr->sin.sin_addr)) == 1) { 35 | addr_set_family(addr, AF_INET); 36 | return 1; 37 | } else if (inet_pton(AF_INET6, buf, &(addr->sin6.sin6_addr)) == 1) { 38 | addr_set_family(addr, AF_INET6); 39 | return 1; 40 | } 41 | return 0; 42 | } 43 | 44 | int addr_to_string(const Address* addr, char* buf, size_t len) { 45 | memset(buf, 0, sizeof(len)); 46 | switch (addr->family) { 47 | case AF_INET6: 48 | return inet_ntop(AF_INET6, &addr->sin6.sin6_addr, buf, len) != NULL; 49 | case AF_INET: 50 | default: 51 | return inet_ntop(AF_INET, &addr->sin.sin_addr, buf, len) != NULL; 52 | } 53 | return 0; 54 | } 55 | 56 | int addr_equal(const Address* a, const Address* b) { 57 | // TODO 58 | return 1; 59 | } 60 | -------------------------------------------------------------------------------- /src/address.h: -------------------------------------------------------------------------------- 1 | #ifndef ADDRESS_H_ 2 | #define ADDRESS_H_ 3 | 4 | #include "config.h" 5 | #if CONFIG_USE_LWIP 6 | #include 7 | #else 8 | #include 9 | #include 10 | #endif 11 | #include 12 | 13 | #define ADDRSTRLEN INET6_ADDRSTRLEN 14 | 15 | typedef struct Address { 16 | uint8_t family; 17 | struct sockaddr_in sin; 18 | struct sockaddr_in6 sin6; 19 | uint16_t port; 20 | } Address; 21 | 22 | void addr_set_family(Address* addr, int family); 23 | 24 | void addr_set_port(Address* addr, uint16_t port); 25 | 26 | int addr_inet6_validate(const char* ipv6, size_t len, Address* addr); 27 | 28 | int addr_inet_validate(const char* ipv4, size_t len, Address* addr); 29 | 30 | int addr_to_string(const Address* addr, char* buf, size_t len); 31 | 32 | int addr_from_string(const char* str, Address* addr); 33 | 34 | int addr_equal(const Address* a, const Address* b); 35 | 36 | #endif // ADDRESS_H_ 37 | -------------------------------------------------------------------------------- /src/agent.h: -------------------------------------------------------------------------------- 1 | #ifndef AGENT_H_ 2 | #define AGENT_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "base64.h" 10 | #include "ice.h" 11 | #include "socket.h" 12 | #include "stun.h" 13 | #include "utils.h" 14 | 15 | #ifndef AGENT_MAX_DESCRIPTION 16 | #define AGENT_MAX_DESCRIPTION 40960 17 | #endif 18 | 19 | #ifndef AGENT_MAX_CANDIDATES 20 | #define AGENT_MAX_CANDIDATES 10 21 | #endif 22 | 23 | #ifndef AGENT_MAX_CANDIDATE_PAIRS 24 | #define AGENT_MAX_CANDIDATE_PAIRS 100 25 | #endif 26 | 27 | typedef enum AgentState { 28 | 29 | AGENT_STATE_GATHERING_ENDED = 0, 30 | AGENT_STATE_GATHERING_STARTED, 31 | AGENT_STATE_GATHERING_COMPLETED, 32 | 33 | } AgentState; 34 | 35 | typedef enum AgentMode { 36 | 37 | AGENT_MODE_CONTROLLED = 0, 38 | AGENT_MODE_CONTROLLING 39 | 40 | } AgentMode; 41 | 42 | typedef struct Agent Agent; 43 | 44 | struct Agent { 45 | char remote_ufrag[ICE_UFRAG_LENGTH + 1]; 46 | char remote_upwd[ICE_UPWD_LENGTH + 1]; 47 | 48 | char local_ufrag[ICE_UFRAG_LENGTH + 1]; 49 | char local_upwd[ICE_UPWD_LENGTH + 1]; 50 | 51 | IceCandidate local_candidates[AGENT_MAX_CANDIDATES]; 52 | IceCandidate remote_candidates[AGENT_MAX_CANDIDATES]; 53 | 54 | int local_candidates_count; 55 | int remote_candidates_count; 56 | 57 | UdpSocket udp_sockets[2]; 58 | 59 | Address host_addr; 60 | int b_host_addr; 61 | uint64_t binding_request_time; 62 | AgentState state; 63 | 64 | AgentMode mode; 65 | 66 | IceCandidatePair candidate_pairs[AGENT_MAX_CANDIDATE_PAIRS]; 67 | IceCandidatePair* selected_pair; 68 | IceCandidatePair* nominated_pair; 69 | 70 | int candidate_pairs_num; 71 | int use_candidate; 72 | uint32_t transaction_id[3]; 73 | }; 74 | 75 | void agent_gather_candidate(Agent* agent, const char* urls, const char* username, const char* credential); 76 | 77 | void agent_get_local_description(Agent* agent, char* description, int length); 78 | 79 | int agent_send(Agent* agent, const uint8_t* buf, int len); 80 | 81 | int agent_recv(Agent* agent, uint8_t* buf, int len); 82 | 83 | void agent_set_remote_description(Agent* agent, char* description); 84 | 85 | int agent_select_candidate_pair(Agent* agent); 86 | 87 | int agent_connectivity_check(Agent* agent); 88 | 89 | void agent_clear_candidates(Agent* agent); 90 | 91 | int agent_create(Agent* agent); 92 | 93 | void agent_destroy(Agent* agent); 94 | 95 | #endif // AGENT_H_ 96 | -------------------------------------------------------------------------------- /src/base64.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 6 | 7 | void base64_encode(const unsigned char* input, int input_len, char* output, int output_len) { 8 | int i, j; 9 | unsigned char buf[3]; 10 | int buf_len; 11 | int output_index = 0; 12 | 13 | for (i = 0; i < input_len; i += 3) { 14 | buf_len = 0; 15 | for (j = 0; j < 3; j++) { 16 | if (i + j < input_len) { 17 | buf[j] = input[i + j]; 18 | buf_len++; 19 | } else { 20 | buf[j] = 0; 21 | } 22 | } 23 | 24 | if (output_index + 4 > output_len) { 25 | return; 26 | } 27 | 28 | output[output_index++] = base64_table[(buf[0] & 0xFC) >> 2]; 29 | output[output_index++] = base64_table[((buf[0] & 0x03) << 4) | ((buf[1] & 0xF0) >> 4)]; 30 | output[output_index++] = (buf_len > 1) ? base64_table[((buf[1] & 0x0F) << 2) | ((buf[2] & 0xC0) >> 6)] : '='; 31 | output[output_index++] = (buf_len > 2) ? base64_table[buf[2] & 0x3F] : '='; 32 | } 33 | 34 | output[output_index] = '\0'; 35 | } 36 | 37 | int base64_decode(const char* input, int input_len, unsigned char* output, int output_len) { 38 | int i, j; 39 | unsigned char buf[4]; 40 | int buf_len; 41 | int output_index = 0; 42 | 43 | for (i = 0; i < input_len; i += 4) { 44 | buf_len = 0; 45 | for (j = 0; j < 4; j++) { 46 | if (i + j < input_len) { 47 | if (input[i + j] != '=') { 48 | buf[j] = strchr(base64_table, input[i + j]) - base64_table; 49 | buf_len++; 50 | } else { 51 | buf[j] = 0; 52 | } 53 | } 54 | } 55 | 56 | if (output_index + buf_len > output_len) { 57 | return -1; 58 | } 59 | 60 | output[output_index++] = (buf[0] << 2) | ((buf[1] & 0x30) >> 4); 61 | if (buf_len > 2) { 62 | output[output_index++] = ((buf[1] & 0x0F) << 4) | ((buf[2] & 0x3C) >> 2); 63 | } 64 | if (buf_len > 3) { 65 | output[output_index++] = ((buf[2] & 0x03) << 6) | buf[3]; 66 | } 67 | } 68 | 69 | return output_index; 70 | } 71 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void base64_encode(const unsigned char* input, int input_len, char* output, int output_len); 4 | 5 | int base64_decode(const char* input, int input_len, unsigned char* output, int output_len); 6 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "buffer.h" 7 | #include "utils.h" 8 | 9 | Buffer* buffer_new(int size) { 10 | Buffer* rb; 11 | rb = (Buffer*)calloc(1, sizeof(Buffer)); 12 | 13 | rb->data = (uint8_t*)calloc(1, size); 14 | rb->size = size; 15 | rb->head = 0; 16 | rb->tail = 0; 17 | 18 | return rb; 19 | } 20 | 21 | void buffer_clear(Buffer* rb) { 22 | rb->head = 0; 23 | rb->tail = 0; 24 | } 25 | 26 | void buffer_free(Buffer* rb) { 27 | if (rb) { 28 | free(rb->data); 29 | rb->data = NULL; 30 | free(rb); 31 | rb = NULL; 32 | } 33 | } 34 | 35 | int buffer_push_tail(Buffer* rb, const uint8_t* data, int size) { 36 | if (!rb || !data || size <= 0) { 37 | return -1; 38 | } 39 | 40 | int free_space = (rb->size + rb->head - rb->tail - 1) % rb->size; 41 | 42 | int align_size = ALIGN32(size + 4); 43 | 44 | if (align_size > free_space) { 45 | LOGE("no enough space"); 46 | return -1; 47 | } 48 | 49 | int tail_end = (rb->tail + align_size) % rb->size; 50 | 51 | if (tail_end < rb->tail) { 52 | if (rb->head < align_size) { 53 | LOGE("no enough space"); 54 | return -1; 55 | } 56 | 57 | int* p = (int*)(rb->data + rb->tail); 58 | *p = size; 59 | memcpy(rb->data, data, size); 60 | rb->tail = size; 61 | 62 | } else { 63 | int* p = (int*)(rb->data + rb->tail); 64 | *p = size; 65 | memcpy(rb->data + rb->tail + 4, data, size); 66 | rb->tail = tail_end; 67 | } 68 | 69 | return size; 70 | } 71 | 72 | uint8_t* buffer_peak_head(Buffer* rb, int* size) { 73 | if (!rb || rb->head == rb->tail) { 74 | return NULL; 75 | } 76 | 77 | *size = *((int*)(rb->data + rb->head)); 78 | 79 | int align_size = ALIGN32(*size + 4); 80 | 81 | int head_end = (rb->head + align_size) % rb->size; 82 | 83 | if (head_end < rb->head) { 84 | return rb->data; 85 | 86 | } else { 87 | return rb->data + (rb->head + 4); 88 | } 89 | } 90 | 91 | void buffer_pop_head(Buffer* rb) { 92 | if (!rb || rb->head == rb->tail) { 93 | return; 94 | } 95 | 96 | int* size = (int*)(rb->data + rb->head); 97 | 98 | int align_size = ALIGN32(*size + 4); 99 | 100 | int head_end = (rb->head + align_size) % rb->size; 101 | 102 | if (head_end < rb->head) { 103 | rb->head = *size; 104 | 105 | } else { 106 | rb->head = rb->head + align_size; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H_ 2 | #define BUFFER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct Buffer { 9 | uint8_t* data; 10 | int size; 11 | int head; 12 | int tail; 13 | 14 | } Buffer; 15 | 16 | Buffer* buffer_new(int size); 17 | 18 | void buffer_free(Buffer* rb); 19 | 20 | uint8_t* buffer_peak_head(Buffer* rb, int* size); 21 | 22 | int buffer_push_tail(Buffer* rb, const uint8_t* data, int size); 23 | 24 | void buffer_pop_head(Buffer* rb); 25 | 26 | void buffer_clear(Buffer* rb); 27 | 28 | #endif // BUFFER_H_ 29 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | // uncomment this if you want to handshake with a aiortc 5 | // #define CONFIG_DTLS_USE_ECDSA 1 6 | 7 | #define SCTP_MTU (1200) 8 | #define CONFIG_MTU (1300) 9 | 10 | #ifndef CONFIG_USE_LWIP 11 | #define CONFIG_USE_LWIP 0 12 | #endif 13 | 14 | #ifndef CONFIG_MBEDTLS_DEBUG 15 | #define CONFIG_MBEDTLS_DEBUG 0 16 | #endif 17 | 18 | #ifndef CONFIG_MBEDTLS_2_X 19 | #define CONFIG_MBEDTLS_2_X 0 20 | #endif 21 | 22 | #if CONFIG_MBEDTLS_2_X 23 | #define RSA_KEY_LENGTH 512 24 | #else 25 | #define RSA_KEY_LENGTH 1024 26 | #endif 27 | 28 | #ifndef CONFIG_DTLS_USE_ECDSA 29 | #define CONFIG_DTLS_USE_ECDSA 0 30 | #endif 31 | 32 | #ifndef CONFIG_USE_USRSCTP 33 | #define CONFIG_USE_USRSCTP 1 34 | #endif 35 | 36 | #ifndef CONFIG_VIDEO_BUFFER_SIZE 37 | #define CONFIG_VIDEO_BUFFER_SIZE (CONFIG_MTU * 256) 38 | #endif 39 | 40 | #ifndef CONFIG_AUDIO_BUFFER_SIZE 41 | #define CONFIG_AUDIO_BUFFER_SIZE (CONFIG_MTU * 256) 42 | #endif 43 | 44 | #ifndef CONFIG_DATA_BUFFER_SIZE 45 | #define CONFIG_DATA_BUFFER_SIZE (SCTP_MTU * 128) 46 | #endif 47 | 48 | #ifndef CONFIG_SDP_BUFFER_SIZE 49 | #define CONFIG_SDP_BUFFER_SIZE 8096 50 | #endif 51 | 52 | #ifndef CONFIG_MQTT_BUFFER_SIZE 53 | #define CONFIG_MQTT_BUFFER_SIZE 4096 54 | #endif 55 | 56 | #ifndef CONFIG_HTTP_BUFFER_SIZE 57 | #define CONFIG_HTTP_BUFFER_SIZE 4096 58 | #endif 59 | 60 | #ifndef CONFIG_TLS_READ_TIMEOUT 61 | #define CONFIG_TLS_READ_TIMEOUT 3000 62 | #endif 63 | 64 | #ifndef CONFIG_KEEPALIVE_TIMEOUT 65 | #define CONFIG_KEEPALIVE_TIMEOUT 10000 66 | #endif 67 | 68 | #ifndef CONFIG_AUDIO_DURATION 69 | #define CONFIG_AUDIO_DURATION 20 70 | #endif 71 | 72 | #define CONFIG_IPV6 0 73 | // empty will use first active interface 74 | #define CONFIG_IFACE_PREFIX "" 75 | 76 | // #define LOG_LEVEL LEVEL_DEBUG 77 | #define LOG_REDIRECT 0 78 | 79 | // Disable MQTT and HTTP signaling 80 | // #define DISABLE_PEER_SIGNALING 1 81 | 82 | #endif // CONFIG_H_ 83 | -------------------------------------------------------------------------------- /src/dtls_srtp.h: -------------------------------------------------------------------------------- 1 | #ifndef DTLS_SRTP_H_ 2 | #define DTLS_SRTP_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "address.h" 19 | 20 | #define SRTP_MASTER_KEY_LENGTH 16 21 | #define SRTP_MASTER_SALT_LENGTH 14 22 | #define DTLS_SRTP_KEY_MATERIAL_LENGTH 60 23 | #define DTLS_SRTP_FINGERPRINT_LENGTH 160 24 | 25 | typedef enum DtlsSrtpRole { 26 | 27 | DTLS_SRTP_ROLE_CLIENT, 28 | DTLS_SRTP_ROLE_SERVER 29 | 30 | } DtlsSrtpRole; 31 | 32 | typedef enum DtlsSrtpState { 33 | 34 | DTLS_SRTP_STATE_INIT, 35 | DTLS_SRTP_STATE_HANDSHAKE, 36 | DTLS_SRTP_STATE_CONNECTED 37 | 38 | } DtlsSrtpState; 39 | 40 | typedef struct DtlsSrtp { 41 | // MbedTLS 42 | mbedtls_ssl_context ssl; 43 | mbedtls_ssl_config conf; 44 | mbedtls_ssl_cookie_ctx cookie_ctx; 45 | mbedtls_x509_crt cert; 46 | mbedtls_pk_context pkey; 47 | mbedtls_entropy_context entropy; 48 | mbedtls_ctr_drbg_context ctr_drbg; 49 | 50 | // SRTP 51 | srtp_policy_t remote_policy; 52 | srtp_policy_t local_policy; 53 | srtp_t srtp_in; 54 | srtp_t srtp_out; 55 | unsigned char remote_policy_key[SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH]; 56 | unsigned char local_policy_key[SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH]; 57 | 58 | int (*udp_send)(void* ctx, const unsigned char* buf, size_t len); 59 | int (*udp_recv)(void* ctx, unsigned char* buf, size_t len); 60 | 61 | Address* remote_addr; 62 | 63 | DtlsSrtpRole role; 64 | DtlsSrtpState state; 65 | 66 | char local_fingerprint[DTLS_SRTP_FINGERPRINT_LENGTH]; 67 | char remote_fingerprint[DTLS_SRTP_FINGERPRINT_LENGTH]; 68 | char actual_remote_fingerprint[DTLS_SRTP_FINGERPRINT_LENGTH]; 69 | 70 | void* user_data; 71 | 72 | } DtlsSrtp; 73 | 74 | int dtls_srtp_init(DtlsSrtp* dtls_srtp, DtlsSrtpRole role, void* user_data); 75 | 76 | void dtls_srtp_deinit(DtlsSrtp* dtls_srtp); 77 | 78 | int dtls_srtp_create_cert(DtlsSrtp* dtls_srtp); 79 | 80 | int dtls_srtp_handshake(DtlsSrtp* dtls_srtp, Address* addr); 81 | 82 | void dtls_srtp_reset_session(DtlsSrtp* dtls_srtp); 83 | 84 | int dtls_srtp_write(DtlsSrtp* dtls_srtp, const uint8_t* buf, size_t len); 85 | 86 | int dtls_srtp_read(DtlsSrtp* dtls_srtp, uint8_t* buf, size_t len); 87 | 88 | void dtls_srtp_sctp_to_dtls(DtlsSrtp* dtls_srtp, uint8_t* packet, int bytes); 89 | 90 | int dtls_srtp_probe(uint8_t* buf); 91 | 92 | void dtls_srtp_decrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); 93 | 94 | void dtls_srtp_decrypt_rtcp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); 95 | 96 | void dtls_srtp_encrypt_rtp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); 97 | 98 | void dtls_srtp_encrypt_rctp_packet(DtlsSrtp* dtls_srtp, uint8_t* packet, int* bytes); 99 | 100 | #endif // DTLS_SRTP_H_ 101 | -------------------------------------------------------------------------------- /src/ice.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ice.h" 9 | #include "mdns.h" 10 | #include "ports.h" 11 | #include "socket.h" 12 | #include "utils.h" 13 | 14 | static uint8_t ice_candidate_type_preference(IceCandidateType type) { 15 | switch (type) { 16 | case ICE_CANDIDATE_TYPE_HOST: 17 | return 126; 18 | case ICE_CANDIDATE_TYPE_SRFLX: 19 | return 100; 20 | case ICE_CANDIDATE_TYPE_RELAY: 21 | return 0; 22 | default: 23 | return 0; 24 | } 25 | } 26 | 27 | static uint16_t ice_candidate_local_preference(IceCandidate* candidate) { 28 | return candidate->addr.port; 29 | } 30 | 31 | static void ice_candidate_priority(IceCandidate* candidate) { 32 | // priority = (2^24)*(type preference) + (2^8)*(local preference) + (256 - component ID) 33 | candidate->priority = (1 << 24) * ice_candidate_type_preference(candidate->type) + (1 << 8) * ice_candidate_local_preference(candidate) + (256 - candidate->component); 34 | } 35 | 36 | void ice_candidate_create(IceCandidate* candidate, int foundation, IceCandidateType type, Address* addr) { 37 | memcpy(&candidate->addr, addr, sizeof(Address)); 38 | candidate->type = type; 39 | 40 | snprintf(candidate->foundation, sizeof(candidate->foundation), "%d", foundation); 41 | // 1: RTP, 2: RTCP 42 | candidate->component = 1; 43 | 44 | ice_candidate_priority(candidate); 45 | 46 | snprintf(candidate->transport, sizeof(candidate->transport), "%s", "UDP"); 47 | } 48 | 49 | void ice_candidate_to_description(IceCandidate* candidate, char* description, int length) { 50 | char addr_string[ADDRSTRLEN]; 51 | char typ_raddr[128]; 52 | 53 | memset(typ_raddr, 0, sizeof(typ_raddr)); 54 | addr_to_string(&candidate->raddr, addr_string, sizeof(addr_string)); 55 | 56 | switch (candidate->type) { 57 | case ICE_CANDIDATE_TYPE_HOST: 58 | snprintf(typ_raddr, sizeof(typ_raddr), "host"); 59 | break; 60 | case ICE_CANDIDATE_TYPE_SRFLX: 61 | snprintf(typ_raddr, sizeof(typ_raddr), "srflx raddr %s rport %d", addr_string, candidate->raddr.port); 62 | break; 63 | case ICE_CANDIDATE_TYPE_RELAY: 64 | snprintf(typ_raddr, sizeof(typ_raddr), "relay raddr %s rport %d", addr_string, candidate->raddr.port); 65 | default: 66 | break; 67 | } 68 | 69 | addr_to_string(&candidate->addr, addr_string, sizeof(addr_string)); 70 | snprintf(description, length, "a=candidate:%s %d %s %" PRIu32 " %s %d typ %s\r\n", 71 | candidate->foundation, 72 | candidate->component, 73 | candidate->transport, 74 | candidate->priority, 75 | addr_string, 76 | candidate->addr.port, 77 | typ_raddr); 78 | } 79 | 80 | int ice_candidate_from_description(IceCandidate* candidate, char* description, char* end) { 81 | char* candidate_start = description; 82 | uint32_t port; 83 | char type[16]; 84 | char addrstring[ADDRSTRLEN]; 85 | 86 | if (strncmp("a=", candidate_start, strlen("a=")) == 0) { 87 | candidate_start += strlen("a="); 88 | } 89 | candidate_start += strlen("candidate:"); 90 | 91 | // a=candidate:448736988 1 udp 2122260223 172.17.0.1 49250 typ host generation 0 network-id 1 network-cost 50 92 | // a=candidate:udpcandidate 1 udp 120 192.168.1.102 8000 typ host 93 | if (sscanf(candidate_start, "%s %d %s %" PRIu32 " %s %" PRIu32 " typ %s", 94 | candidate->foundation, 95 | &candidate->component, 96 | candidate->transport, 97 | &candidate->priority, 98 | addrstring, 99 | &port, 100 | type) != 7) { 101 | LOGE("Failed to parse ICE candidate description"); 102 | return -1; 103 | } 104 | 105 | if (strncmp(candidate->transport, "UDP", 3) != 0 && strncmp(candidate->transport, "udp", 3) != 0) { 106 | LOGE("Only UDP transport is supported"); 107 | return -1; 108 | } 109 | 110 | if (strncmp(type, "host", 4) == 0) { 111 | candidate->type = ICE_CANDIDATE_TYPE_HOST; 112 | } else if (strncmp(type, "srflx", 5) == 0) { 113 | candidate->type = ICE_CANDIDATE_TYPE_SRFLX; 114 | } else if (strncmp(type, "relay", 5) == 0) { 115 | candidate->type = ICE_CANDIDATE_TYPE_RELAY; 116 | } else { 117 | LOGE("Unknown candidate type: %s", type); 118 | return -1; 119 | } 120 | 121 | addr_set_port(&candidate->addr, port); 122 | 123 | if (strstr(addrstring, "local") != NULL) { 124 | if (mdns_resolve_addr(addrstring, &candidate->addr) == 0) { 125 | LOGW("Failed to resolve mDNS address"); 126 | return -1; 127 | } 128 | } else if (addr_from_string(addrstring, &candidate->addr) == 0) { 129 | return -1; 130 | } 131 | 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /src/ice.h: -------------------------------------------------------------------------------- 1 | #ifndef ICE_H_ 2 | #define ICE_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "address.h" 8 | #include "stun.h" 9 | 10 | #define ICE_UFRAG_LENGTH 256 11 | #define ICE_UPWD_LENGTH 256 12 | 13 | typedef enum IceCandidateState { 14 | 15 | ICE_CANDIDATE_STATE_FROZEN, 16 | ICE_CANDIDATE_STATE_WAITING, 17 | ICE_CANDIDATE_STATE_INPROGRESS, 18 | ICE_CANDIDATE_STATE_SUCCEEDED, 19 | ICE_CANDIDATE_STATE_FAILED, 20 | 21 | } IceCandidateState; 22 | 23 | typedef enum IceCandidateType { 24 | 25 | ICE_CANDIDATE_TYPE_HOST, 26 | ICE_CANDIDATE_TYPE_SRFLX, 27 | ICE_CANDIDATE_TYPE_PRFLX, 28 | ICE_CANDIDATE_TYPE_RELAY, 29 | 30 | } IceCandidateType; 31 | 32 | typedef struct IceCandidate IceCandidate; 33 | 34 | struct IceCandidate { 35 | char foundation[32 + 1]; 36 | int component; 37 | uint32_t priority; 38 | char transport[32 + 1]; 39 | IceCandidateType type; 40 | IceCandidateState state; 41 | Address addr; 42 | Address raddr; 43 | }; 44 | 45 | typedef struct IceCandidatePair IceCandidatePair; 46 | 47 | struct IceCandidatePair { 48 | IceCandidateState state; 49 | IceCandidate* local; 50 | IceCandidate* remote; 51 | int conncheck; 52 | uint64_t priority; 53 | }; 54 | 55 | void ice_candidate_create(IceCandidate* ice_candidate, int foundation, IceCandidateType type, Address* addr); 56 | 57 | void ice_candidate_to_description(IceCandidate* candidate, char* description, int length); 58 | 59 | int ice_candidate_from_description(IceCandidate* candidate, char* description, char* end); 60 | 61 | int ice_candidate_get_local_address(IceCandidate* candidate, Address* address); 62 | 63 | #endif // ICE_H_ 64 | -------------------------------------------------------------------------------- /src/mdns.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "address.h" 8 | #include "socket.h" 9 | #include "utils.h" 10 | 11 | #define MDNS_GROUP "224.0.0.251" 12 | #define MDNS_PORT 5353 13 | 14 | typedef struct DnsHeader { 15 | uint16_t id; 16 | uint16_t flags; 17 | uint16_t qestions; 18 | uint16_t answer_rrs; 19 | uint16_t authority_rrs; 20 | uint16_t additional_rrs; 21 | } DnsHeader; 22 | 23 | typedef struct DnsAnswer { 24 | uint16_t type; 25 | uint16_t class; 26 | uint32_t ttl; 27 | uint16_t length; 28 | uint8_t data[0]; 29 | } DnsAnswer; 30 | 31 | typedef struct DnsQuery { 32 | uint16_t type; 33 | uint16_t class; 34 | } DnsQuery; 35 | 36 | static int mdns_add_hostname(const char* hostname, uint8_t* buf, int size) { 37 | const char *label, *dot; 38 | int len, offset; 39 | 40 | offset = 0; 41 | label = hostname; 42 | while (*label) { 43 | dot = strchr(label, '.'); 44 | if (!dot) { 45 | dot = label + strlen(label); 46 | } 47 | len = dot - label; 48 | buf[offset++] = len; 49 | memcpy(buf + offset, label, len); 50 | offset += len; 51 | label = *dot ? dot + 1 : dot; 52 | } 53 | buf[offset++] = 0x00; 54 | return offset; 55 | } 56 | static int mdns_parse_answer(uint8_t* buf, int size, Address* addr, const char* hostname) { 57 | int flags_qr, offset; 58 | DnsHeader* header; 59 | DnsAnswer* answer; 60 | uint8_t name[256]; 61 | 62 | if (size < sizeof(DnsHeader)) { 63 | LOGE("response too short"); 64 | return -1; 65 | } 66 | 67 | header = (DnsHeader*)buf; 68 | flags_qr = ntohs(header->flags) >> 15; 69 | if (flags_qr != 1) { 70 | LOGD("flag is not a DNS response"); 71 | return -1; 72 | } 73 | 74 | buf += sizeof(DnsHeader); 75 | offset = mdns_add_hostname(hostname, name, sizeof(name)); 76 | // compare hostname 77 | if (memcmp(buf, name, offset)) { 78 | LOGI("not a mDNS response"); 79 | return -1; 80 | } 81 | 82 | answer = (DnsAnswer*)(buf + offset); 83 | LOGD("type: %" PRIu16 ", class: %" PRIu16 ", ttl: %" PRIu32 ", length: %" PRIu16 "", ntohs(answer->type), ntohs(answer->class), ntohl(answer->ttl), ntohs(answer->length)); 84 | if (ntohs(answer->length) != 4) { 85 | LOGI("invalid length"); 86 | return -1; 87 | } 88 | 89 | memcpy(&addr->sin.sin_addr, answer->data, 4); 90 | return 0; 91 | } 92 | 93 | static int mdns_build_query(const char* hostname, uint8_t* buf, int size) { 94 | int total_size, offset; 95 | DnsHeader* dns_header; 96 | DnsQuery* dns_query; 97 | 98 | total_size = sizeof(DnsHeader) + strlen(hostname) + sizeof(DnsQuery) + 2; 99 | if (size < total_size) { 100 | printf("buf size is not enough"); 101 | return -1; 102 | } 103 | 104 | memset(buf, 0, size); 105 | dns_header = (DnsHeader*)buf; 106 | dns_header->qestions = 0x0100; 107 | offset = sizeof(DnsHeader); 108 | 109 | // Append hostname to query 110 | offset += mdns_add_hostname(hostname, buf + offset, size - offset); 111 | 112 | dns_query = (DnsQuery*)(buf + offset); 113 | dns_query->type = 0x0100; 114 | dns_query->class = 0x0100; 115 | return total_size; 116 | } 117 | 118 | int mdns_resolve_addr(const char* hostname, Address* addr) { 119 | Address mcast_addr = {0}; 120 | UdpSocket udp_socket; 121 | uint8_t buf[256]; 122 | char addr_string[ADDRSTRLEN]; 123 | struct timeval tv = {1, 0}; 124 | fd_set rfds; 125 | int maxfd, send_retry, recv_retry, size, ret; 126 | 127 | if (udp_socket_open(&udp_socket, AF_INET, MDNS_PORT) < 0) { 128 | LOGE("Failed to create socket"); 129 | return -1; 130 | } 131 | 132 | addr_from_string(MDNS_GROUP, &mcast_addr); 133 | addr_set_port(&mcast_addr, MDNS_PORT); 134 | if (udp_socket_add_multicast_group(&udp_socket, &mcast_addr) < 0) { 135 | LOGE("Failed to add multicast group"); 136 | return -1; 137 | } 138 | 139 | maxfd = udp_socket.fd; 140 | FD_ZERO(&rfds); 141 | 142 | for (send_retry = 3; send_retry > 0; send_retry--) { 143 | size = mdns_build_query(hostname, buf, sizeof(buf)); 144 | udp_socket_sendto(&udp_socket, &mcast_addr, buf, size); 145 | for (recv_retry = 5; recv_retry > 0; recv_retry--) { 146 | FD_SET(udp_socket.fd, &rfds); 147 | ret = select(maxfd + 1, &rfds, NULL, NULL, &tv); 148 | 149 | if (ret < 0) { 150 | LOGE("select error"); 151 | break; 152 | } else if (ret > 0 && FD_ISSET(udp_socket.fd, &rfds)) { 153 | ret = udp_socket_recvfrom(&udp_socket, NULL, buf, sizeof(buf)); 154 | if (!mdns_parse_answer(buf, ret, addr, hostname)) { 155 | addr_to_string(addr, addr_string, sizeof(addr_string)); 156 | addr_set_family(addr, AF_INET); 157 | LOGI("Resolved %s -> %s", hostname, addr_string); 158 | udp_socket_close(&udp_socket); 159 | return 1; 160 | } else { 161 | LOGD("timeout"); 162 | } 163 | } 164 | } 165 | } 166 | 167 | LOGI("Failed to resolve hostname"); 168 | udp_socket_close(&udp_socket); 169 | return 0; 170 | } 171 | -------------------------------------------------------------------------------- /src/mdns.h: -------------------------------------------------------------------------------- 1 | #ifndef MDNS_H_ 2 | #define MDNS_H_ 3 | 4 | #include 5 | #include "address.h" 6 | 7 | int mdns_resolve_addr(const char* hostname, Address* addr); 8 | 9 | #endif // MDNS_H_ 10 | -------------------------------------------------------------------------------- /src/peer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "peer.h" 7 | #include "utils.h" 8 | 9 | int peer_init() { 10 | if (srtp_init() != srtp_err_status_ok) { 11 | LOGE("libsrtp init failed"); 12 | } 13 | 14 | return 0; 15 | } 16 | 17 | void peer_deinit() { 18 | srtp_shutdown(); 19 | } 20 | -------------------------------------------------------------------------------- /src/peer.h: -------------------------------------------------------------------------------- 1 | #ifndef PEER_H_ 2 | #define PEER_H_ 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "peer_connection.h" 9 | #include "peer_signaling.h" 10 | 11 | int peer_init(); 12 | 13 | void peer_deinit(); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif // PEER_H_ 20 | -------------------------------------------------------------------------------- /src/peer_connection.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file peer_connection.h 3 | * @brief Struct PeerConnection 4 | */ 5 | #ifndef PEER_CONNECTION_H_ 6 | #define PEER_CONNECTION_H_ 7 | 8 | #include 9 | #include 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | typedef enum PeerConnectionState { 16 | 17 | PEER_CONNECTION_CLOSED = 0, 18 | PEER_CONNECTION_NEW, 19 | PEER_CONNECTION_CHECKING, 20 | PEER_CONNECTION_CONNECTED, 21 | PEER_CONNECTION_COMPLETED, 22 | PEER_CONNECTION_FAILED, 23 | PEER_CONNECTION_DISCONNECTED, 24 | 25 | } PeerConnectionState; 26 | 27 | typedef enum DataChannelType { 28 | 29 | DATA_CHANNEL_NONE = 0, 30 | DATA_CHANNEL_STRING, 31 | DATA_CHANNEL_BINARY, 32 | 33 | } DataChannelType; 34 | 35 | typedef enum DecpChannelType { 36 | DATA_CHANNEL_RELIABLE = 0x00, 37 | DATA_CHANNEL_RELIABLE_UNORDERED = 0x80, 38 | DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT = 0x01, 39 | DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED = 0x81, 40 | DATA_CHANNEL_PARTIAL_RELIABLE_TIMED = 0x02, 41 | DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED = 0x82, 42 | } DecpChannelType; 43 | 44 | typedef enum MediaCodec { 45 | 46 | CODEC_NONE = 0, 47 | 48 | /* Video */ 49 | CODEC_H264, 50 | CODEC_VP8, // not implemented yet 51 | CODEC_MJPEG, // not implemented yet 52 | 53 | /* Audio */ 54 | CODEC_OPUS, // not implemented yet 55 | CODEC_PCMA, 56 | CODEC_PCMU, 57 | 58 | } MediaCodec; 59 | 60 | typedef struct IceServer { 61 | const char* urls; 62 | const char* username; 63 | const char* credential; 64 | 65 | } IceServer; 66 | 67 | typedef struct PeerConfiguration { 68 | IceServer ice_servers[5]; 69 | 70 | MediaCodec audio_codec; 71 | MediaCodec video_codec; 72 | DataChannelType datachannel; 73 | 74 | void (*onaudiotrack)(uint8_t* data, size_t size, void* userdata); 75 | void (*onvideotrack)(uint8_t* data, size_t size, void* userdata); 76 | void (*on_request_keyframe)(void* userdata); 77 | void* user_data; 78 | 79 | } PeerConfiguration; 80 | 81 | typedef struct PeerConnection PeerConnection; 82 | 83 | const char* peer_connection_state_to_string(PeerConnectionState state); 84 | 85 | PeerConnectionState peer_connection_get_state(PeerConnection* pc); 86 | 87 | void* peer_connection_get_sctp(PeerConnection* pc); 88 | 89 | PeerConnection* peer_connection_create(PeerConfiguration* config); 90 | 91 | void peer_connection_destroy(PeerConnection* pc); 92 | 93 | void peer_connection_close(PeerConnection* pc); 94 | 95 | int peer_connection_loop(PeerConnection* pc); 96 | 97 | int peer_connection_create_datachannel(PeerConnection* pc, DecpChannelType channel_type, uint16_t priority, uint32_t reliability_parameter, char* label, char* protocol); 98 | 99 | int peer_connection_create_datachannel_sid(PeerConnection* pc, DecpChannelType channel_type, uint16_t priority, uint32_t reliability_parameter, char* label, char* protocol, uint16_t sid); 100 | 101 | /** 102 | * @brief send message to data channel 103 | * @param[in] peer connection 104 | * @param[in] message buffer 105 | * @param[in] length of message 106 | */ 107 | int peer_connection_datachannel_send(PeerConnection* pc, char* message, size_t len); 108 | 109 | int peer_connection_datachannel_send_sid(PeerConnection* pc, char* message, size_t len, uint16_t sid); 110 | 111 | int peer_connection_send_audio(PeerConnection* pc, const uint8_t* packet, size_t bytes); 112 | 113 | int peer_connection_send_video(PeerConnection* pc, const uint8_t* packet, size_t bytes); 114 | 115 | void peer_connection_set_remote_description(PeerConnection* pc, const char* sdp); 116 | 117 | void peer_connection_create_offer(PeerConnection* pc); 118 | 119 | /** 120 | * @brief register callback function to handle packet loss from RTCP receiver report 121 | * @param[in] peer connection 122 | * @param[in] callback function void (*cb)(float fraction_loss, uint32_t total_loss, void *userdata) 123 | * @param[in] userdata for callback function 124 | */ 125 | void peer_connection_on_receiver_packet_loss(PeerConnection* pc, 126 | void (*on_receiver_packet_loss)(float fraction_loss, uint32_t total_loss, void* userdata)); 127 | 128 | /** 129 | * @brief Set the callback function to handle onicecandidate event. 130 | * @param A PeerConnection. 131 | * @param A callback function to handle onicecandidate event. 132 | * @param A userdata which is pass to callback function. 133 | */ 134 | void peer_connection_onicecandidate(PeerConnection* pc, void (*onicecandidate)(char* sdp_text, void* userdata)); 135 | 136 | /** 137 | * @brief Set the callback function to handle oniceconnectionstatechange event. 138 | * @param A PeerConnection. 139 | * @param A callback function to handle oniceconnectionstatechange event. 140 | * @param A userdata which is pass to callback function. 141 | */ 142 | void peer_connection_oniceconnectionstatechange(PeerConnection* pc, 143 | void (*oniceconnectionstatechange)(PeerConnectionState state, void* userdata)); 144 | 145 | /** 146 | * @brief register callback function to handle event of datachannel 147 | * @param[in] peer connection 148 | * @param[in] callback function when message received 149 | * @param[in] callback function when connection is opened 150 | * @param[in] callback function when connection is closed 151 | */ 152 | void peer_connection_ondatachannel(PeerConnection* pc, 153 | void (*onmessage)(char* msg, size_t len, void* userdata, uint16_t sid), 154 | void (*onopen)(void* userdata), 155 | void (*onclose)(void* userdata)); 156 | 157 | int peer_connection_lookup_sid(PeerConnection* pc, const char* label, uint16_t* sid); 158 | 159 | char* peer_connection_lookup_sid_label(PeerConnection* pc, uint16_t sid); 160 | 161 | /** 162 | * @brief adds a new remote candidate to the peer connection 163 | * @param[in] peer connection 164 | * @param[in] ice candidate 165 | */ 166 | int peer_connection_add_ice_candidate(PeerConnection* pc, char* ice_candidate); 167 | 168 | #ifdef __cplusplus 169 | } 170 | #endif 171 | 172 | #endif // PEER_CONNECTION_H_ 173 | -------------------------------------------------------------------------------- /src/peer_signaling.c: -------------------------------------------------------------------------------- 1 | #ifndef DISABLE_PEER_SIGNALING 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "base64.h" 12 | #include "config.h" 13 | #include "peer_signaling.h" 14 | #include "ports.h" 15 | #include "ssl_transport.h" 16 | #include "utils.h" 17 | 18 | #define KEEP_ALIVE_TIMEOUT_SECONDS 60 19 | #define CONNACK_RECV_TIMEOUT_MS 1000 20 | 21 | #define PATH_MAX_LEN 128 22 | #define HOST_MAX_LEN 64 23 | #define URL_MAX_LEN (PATH_MAX_LEN + HOST_MAX_LEN + 8) 24 | #define TOPIC_MAX_LEN URL_MAX_LEN 25 | #define TOKEN_MAX_LEN 256 26 | 27 | #define RPC_VERSION "2.0" 28 | #define RPC_METHOD_STATE "state" 29 | #define RPC_METHOD_OFFER "offer" 30 | #define RPC_METHOD_ANSWER "answer" 31 | #define RPC_METHOD_CLOSE "close" 32 | 33 | #define RPC_ERROR_PARSE_ERROR "{\"code\":-32700,\"message\":\"Parse error\"}" 34 | #define RPC_ERROR_INVALID_REQUEST "{\"code\":-32600,\"message\":\"Invalid Request\"}" 35 | #define RPC_ERROR_METHOD_NOT_FOUND "{\"code\":-32601,\"message\":\"Method not found\"}" 36 | #define RPC_ERROR_INVALID_PARAMS "{\"code\":-32602,\"message\":\"Invalid params\"}" 37 | #define RPC_ERROR_INTERNAL_ERROR "{\"code\":-32603,\"message\":\"Internal error\"}" 38 | 39 | typedef struct PeerSignaling { 40 | MQTTContext_t mqtt_ctx; 41 | MQTTFixedBuffer_t mqtt_fixed_buf; 42 | 43 | TransportInterface_t transport; 44 | NetworkContext_t net_ctx; 45 | 46 | uint8_t mqtt_buf[CONFIG_MQTT_BUFFER_SIZE]; 47 | uint8_t http_buf[CONFIG_HTTP_BUFFER_SIZE]; 48 | 49 | char subtopic[TOPIC_MAX_LEN]; 50 | char pubtopic[TOPIC_MAX_LEN]; 51 | 52 | uint16_t packet_id; 53 | int id; 54 | 55 | int proto; // 0: MQTT, 1: HTTP 56 | int port; 57 | char host[HOST_MAX_LEN]; 58 | char path[PATH_MAX_LEN]; 59 | char token[TOKEN_MAX_LEN]; 60 | char client_id[32]; 61 | 62 | PeerConnection* pc; 63 | 64 | } PeerSignaling; 65 | 66 | static PeerSignaling g_ps = {0}; 67 | 68 | static int peer_signaling_resolve_token(const char* token, char* username, char* password) { 69 | char plaintext[TOKEN_MAX_LEN] = {0}; 70 | char* colon; 71 | 72 | if (token == NULL || strlen(token) == 0) { 73 | LOGW("Invalid token"); 74 | return -1; 75 | } 76 | base64_decode(token, strlen(token), (unsigned char*)plaintext, sizeof(plaintext)); 77 | colon = strchr(plaintext, ':'); 78 | if (colon == NULL) { 79 | LOGW("Invalid token: %s", token); 80 | return -1; 81 | } 82 | 83 | strncpy(username, plaintext, colon - plaintext); 84 | strncpy(password, colon + 1, strlen(colon + 1)); 85 | LOGD("Username: %s, Password: %s", username, password); 86 | return 0; 87 | } 88 | 89 | static int peer_signaling_resolve_url(const char* url, char* host, int* port, char* path) { 90 | char *port_start, *path_start; 91 | int proto = 0; 92 | 93 | if (url == NULL || strlen(url) == 0) { 94 | LOGW("Invalid URL"); 95 | return -1; 96 | } 97 | 98 | if (strncmp(url, "mqtts://", 8) == 0) { 99 | *port = 8883; 100 | url += 8; 101 | } else if (strncmp(url, "https://", 8) == 0) { 102 | *port = 443; 103 | url += 8; 104 | proto = 1; 105 | } else if (strncmp(url, "mqtt://", 7) == 0) { 106 | *port = 1883; 107 | url += 7; 108 | } else if (strncmp(url, "http://", 7) == 0) { 109 | *port = 80; 110 | url += 7; 111 | proto = 1; 112 | } else { 113 | LOGW("Invalid URL: %s", url); 114 | return -1; 115 | } 116 | 117 | port_start = strchr(url, ':'); 118 | path_start = strchr(url, '/'); 119 | 120 | if (port_start != NULL && path_start != NULL && port_start < path_start) { 121 | strncpy(host, url, port_start - url); 122 | strncpy(path, path_start, strlen(path_start)); 123 | *port = atoi(port_start + 1); 124 | } else if (port_start == NULL && path_start != NULL) { 125 | strncpy(host, url, path_start - url); 126 | strncpy(path, path_start, strlen(path_start)); 127 | } else if (port_start != NULL && path_start == NULL) { 128 | strncpy(host, url, port_start - url); 129 | *port = atoi(port_start + 1); 130 | } else { 131 | strncpy(host, url, strlen(url)); 132 | } 133 | 134 | LOGI("Host: %s, Port: %d, Path: %s", host, *port, path); 135 | return proto; 136 | } 137 | 138 | static void peer_signaling_mqtt_publish(MQTTContext_t* mqtt_ctx, const char* message) { 139 | MQTTStatus_t status; 140 | MQTTPublishInfo_t pub_info; 141 | 142 | memset(&pub_info, 0, sizeof(pub_info)); 143 | 144 | pub_info.qos = MQTTQoS0; 145 | pub_info.retain = false; 146 | pub_info.pTopicName = g_ps.pubtopic; 147 | pub_info.topicNameLength = strlen(g_ps.pubtopic); 148 | pub_info.pPayload = message; 149 | pub_info.payloadLength = strlen(message); 150 | 151 | status = MQTT_Publish(mqtt_ctx, &pub_info, MQTT_GetPacketId(mqtt_ctx)); 152 | if (status != MQTTSuccess) { 153 | LOGE("MQTT_Publish failed: Status=%s.", MQTT_Status_strerror(status)); 154 | } else { 155 | LOGD("MQTT_Publish succeeded."); 156 | } 157 | } 158 | 159 | static void peer_signaling_on_pub_event(const char* msg, size_t size) { 160 | cJSON *req, *res, *item, *result, *error; 161 | int id = -1; 162 | char* payload = NULL; 163 | PeerConnectionState state; 164 | 165 | req = res = item = result = error = NULL; 166 | state = peer_connection_get_state(g_ps.pc); 167 | do { 168 | req = cJSON_Parse(msg); 169 | if (!req) { 170 | error = cJSON_CreateRaw(RPC_ERROR_PARSE_ERROR); 171 | LOGW("Parse json failed"); 172 | break; 173 | } 174 | 175 | item = cJSON_GetObjectItem(req, "id"); 176 | if (!item && !cJSON_IsNumber(item)) { 177 | error = cJSON_CreateRaw(RPC_ERROR_INVALID_REQUEST); 178 | LOGW("Cannot find id"); 179 | break; 180 | } 181 | 182 | id = item->valueint; 183 | 184 | item = cJSON_GetObjectItem(req, "method"); 185 | if (!item && cJSON_IsString(item)) { 186 | error = cJSON_CreateRaw(RPC_ERROR_INVALID_REQUEST); 187 | LOGW("Cannot find method"); 188 | break; 189 | } 190 | 191 | if (strcmp(item->valuestring, RPC_METHOD_OFFER) == 0) { 192 | switch (state) { 193 | case PEER_CONNECTION_NEW: 194 | case PEER_CONNECTION_DISCONNECTED: 195 | case PEER_CONNECTION_FAILED: 196 | case PEER_CONNECTION_CLOSED: { 197 | g_ps.id = id; 198 | peer_connection_create_offer(g_ps.pc); 199 | } break; 200 | default: { 201 | error = cJSON_CreateRaw(RPC_ERROR_INTERNAL_ERROR); 202 | } break; 203 | } 204 | } else if (strcmp(item->valuestring, RPC_METHOD_ANSWER) == 0) { 205 | item = cJSON_GetObjectItem(req, "params"); 206 | if (!item && !cJSON_IsString(item)) { 207 | error = cJSON_CreateRaw(RPC_ERROR_INVALID_PARAMS); 208 | LOGW("Cannot find params"); 209 | break; 210 | } 211 | 212 | if (state == PEER_CONNECTION_NEW) { 213 | peer_connection_set_remote_description(g_ps.pc, item->valuestring); 214 | result = cJSON_CreateString(""); 215 | } 216 | 217 | } else if (strcmp(item->valuestring, RPC_METHOD_STATE) == 0) { 218 | result = cJSON_CreateString(peer_connection_state_to_string(state)); 219 | 220 | } else if (strcmp(item->valuestring, RPC_METHOD_CLOSE) == 0) { 221 | peer_connection_close(g_ps.pc); 222 | result = cJSON_CreateString(""); 223 | 224 | } else { 225 | error = cJSON_CreateRaw(RPC_ERROR_METHOD_NOT_FOUND); 226 | LOGW("Unsupport method"); 227 | } 228 | 229 | } while (0); 230 | 231 | if (result || error) { 232 | res = cJSON_CreateObject(); 233 | cJSON_AddStringToObject(res, "jsonrpc", RPC_VERSION); 234 | cJSON_AddNumberToObject(res, "id", id); 235 | 236 | if (result) { 237 | cJSON_AddItemToObject(res, "result", result); 238 | } else if (error) { 239 | cJSON_AddItemToObject(res, "error", error); 240 | } 241 | 242 | payload = cJSON_PrintUnformatted(res); 243 | 244 | if (payload) { 245 | peer_signaling_mqtt_publish(&g_ps.mqtt_ctx, payload); 246 | free(payload); 247 | } 248 | cJSON_Delete(res); 249 | } 250 | 251 | if (req) { 252 | cJSON_Delete(req); 253 | } 254 | } 255 | 256 | HTTPResponse_t peer_signaling_http_request(const TransportInterface_t* transport_interface, 257 | const char* method, 258 | size_t method_len, 259 | const char* host, 260 | size_t host_len, 261 | const char* path, 262 | size_t path_len, 263 | const char* auth, 264 | size_t auth_len, 265 | const char* body, 266 | size_t body_len) { 267 | HTTPStatus_t status = HTTPSuccess; 268 | HTTPRequestInfo_t request_info = {0}; 269 | HTTPResponse_t response = {0}; 270 | HTTPRequestHeaders_t request_headers = {0}; 271 | 272 | request_info.pMethod = method; 273 | request_info.methodLen = method_len; 274 | request_info.pHost = host; 275 | request_info.hostLen = host_len; 276 | request_info.pPath = path; 277 | request_info.pathLen = path_len; 278 | request_info.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG; 279 | 280 | request_headers.pBuffer = g_ps.http_buf; 281 | request_headers.bufferLen = sizeof(g_ps.http_buf); 282 | 283 | status = HTTPClient_InitializeRequestHeaders(&request_headers, &request_info); 284 | 285 | if (status == HTTPSuccess) { 286 | HTTPClient_AddHeader(&request_headers, 287 | "Content-Type", strlen("Content-Type"), "application/sdp", strlen("application/sdp")); 288 | 289 | if (auth_len > 0) { 290 | HTTPClient_AddHeader(&request_headers, 291 | "Authorization", strlen("Authorization"), auth, auth_len); 292 | } 293 | 294 | response.pBuffer = g_ps.http_buf; 295 | response.bufferLen = sizeof(g_ps.http_buf); 296 | 297 | status = HTTPClient_Send(transport_interface, 298 | &request_headers, (uint8_t*)body, body ? body_len : 0, &response, 0); 299 | 300 | } else { 301 | LOGE("Failed to initialize HTTP request headers: Error=%s.", HTTPClient_strerror(status)); 302 | } 303 | 304 | return response; 305 | } 306 | 307 | static int peer_signaling_http_post(const char* hostname, const char* path, int port, const char* auth, const char* body) { 308 | int ret = 0; 309 | TransportInterface_t trans_if = {0}; 310 | NetworkContext_t net_ctx; 311 | HTTPResponse_t res; 312 | 313 | trans_if.recv = ssl_transport_recv; 314 | trans_if.send = ssl_transport_send; 315 | trans_if.pNetworkContext = &net_ctx; 316 | 317 | assert(port > 0); 318 | 319 | ret = ssl_transport_connect(&net_ctx, hostname, port, NULL); 320 | 321 | if (ret < 0) { 322 | LOGE("Failed to connect to %s:%d", hostname, port); 323 | return ret; 324 | } 325 | 326 | res = peer_signaling_http_request(&trans_if, "POST", 4, hostname, strlen(hostname), path, 327 | strlen(path), auth, strlen(auth), body, strlen(body)); 328 | 329 | ssl_transport_disconnect(&net_ctx); 330 | 331 | if (res.pHeaders == NULL) { 332 | LOGE("Response headers are NULL"); 333 | return -1; 334 | } 335 | 336 | if (res.pBody == NULL) { 337 | LOGE("Response body is NULL"); 338 | return -1; 339 | } 340 | 341 | LOGI( 342 | "Received HTTP response from %s%s\n" 343 | "Response Headers: %s\nResponse Status: %u\nResponse Body: %s\n", 344 | hostname, path, res.pHeaders, res.statusCode, res.pBody); 345 | 346 | if (res.statusCode == 201) { 347 | peer_connection_set_remote_description(g_ps.pc, (const char*)res.pBody); 348 | } 349 | return 0; 350 | } 351 | 352 | static void peer_signaling_mqtt_event_cb(MQTTContext_t* mqtt_ctx, 353 | MQTTPacketInfo_t* packet_info, 354 | MQTTDeserializedInfo_t* deserialized_info) { 355 | MQTTStatus_t status = MQTTSuccess; 356 | switch (packet_info->type) { 357 | case MQTT_PACKET_TYPE_PUBLISH: 358 | LOGD("MQTT received message: %.*s", 359 | deserialized_info->pPublishInfo->payloadLength, 360 | (char*)deserialized_info->pPublishInfo->pPayload); 361 | peer_signaling_on_pub_event(deserialized_info->pPublishInfo->pPayload, 362 | deserialized_info->pPublishInfo->payloadLength); 363 | break; 364 | case MQTT_PACKET_TYPE_SUBACK: { 365 | size_t ncodes = 0; 366 | int i = 0; 367 | uint8_t* codes = NULL; 368 | status = MQTT_GetSubAckStatusCodes(packet_info, &codes, &ncodes); 369 | 370 | assert(status == MQTTSuccess); 371 | 372 | assert(ncodes == 1); 373 | 374 | for (i = 0; i < ncodes; i++) { 375 | if (codes[0] == MQTTSubAckFailure) { 376 | LOGE("MQTT Subscription failed. Please check authorization"); 377 | break; 378 | } 379 | } 380 | 381 | if (i == ncodes) { 382 | LOGI("MQTT Subscribe succeeded."); 383 | } 384 | } break; 385 | case MQTT_PACKET_TYPE_UNSUBACK: 386 | LOGI("MQTT Unsubscribe succeeded."); 387 | break; 388 | default: 389 | break; 390 | } 391 | } 392 | 393 | static int peer_signaling_mqtt_connect(const char* hostname, int port) { 394 | MQTTStatus_t status; 395 | MQTTConnectInfo_t conn_info; 396 | bool session_present; 397 | char username[TOKEN_MAX_LEN] = {0}; 398 | char password[TOKEN_MAX_LEN] = {0}; 399 | 400 | if (ssl_transport_connect(&g_ps.net_ctx, hostname, port, NULL) < 0) { 401 | LOGE("ssl transport connect failed"); 402 | return -1; 403 | } 404 | 405 | g_ps.transport.recv = ssl_transport_recv; 406 | g_ps.transport.send = ssl_transport_send; 407 | g_ps.transport.pNetworkContext = &g_ps.net_ctx; 408 | g_ps.mqtt_fixed_buf.pBuffer = g_ps.mqtt_buf; 409 | g_ps.mqtt_fixed_buf.size = sizeof(g_ps.mqtt_buf); 410 | status = MQTT_Init(&g_ps.mqtt_ctx, &g_ps.transport, 411 | ports_get_epoch_time, peer_signaling_mqtt_event_cb, &g_ps.mqtt_fixed_buf); 412 | 413 | memset(&conn_info, 0, sizeof(conn_info)); 414 | 415 | conn_info.cleanSession = false; 416 | 417 | if (strlen(g_ps.token) > 0) { 418 | peer_signaling_resolve_token(g_ps.token, username, password); 419 | conn_info.pUserName = username; 420 | conn_info.userNameLength = strlen(username); 421 | conn_info.pPassword = password; 422 | conn_info.passwordLength = strlen(password); 423 | } 424 | 425 | if (strlen(g_ps.client_id) > 0) { 426 | conn_info.pClientIdentifier = g_ps.client_id; 427 | conn_info.clientIdentifierLength = strlen(g_ps.client_id); 428 | } 429 | 430 | conn_info.keepAliveSeconds = KEEP_ALIVE_TIMEOUT_SECONDS; 431 | 432 | status = MQTT_Connect(&g_ps.mqtt_ctx, 433 | &conn_info, NULL, CONNACK_RECV_TIMEOUT_MS, &session_present); 434 | 435 | if (status != MQTTSuccess) { 436 | LOGE("MQTT_Connect failed: Status=%s.", MQTT_Status_strerror(status)); 437 | return -1; 438 | } 439 | 440 | LOGI("MQTT_Connect succeeded."); 441 | return 0; 442 | } 443 | 444 | static int peer_signaling_mqtt_subscribe(int subscribed) { 445 | MQTTStatus_t status = MQTTSuccess; 446 | MQTTSubscribeInfo_t sub_info; 447 | 448 | uint16_t packet_id = MQTT_GetPacketId(&g_ps.mqtt_ctx); 449 | 450 | memset(&sub_info, 0, sizeof(sub_info)); 451 | sub_info.qos = MQTTQoS0; 452 | sub_info.pTopicFilter = g_ps.subtopic; 453 | sub_info.topicFilterLength = strlen(g_ps.subtopic); 454 | 455 | if (subscribed) { 456 | LOGI("Subscribing topic %s", g_ps.subtopic); 457 | status = MQTT_Subscribe(&g_ps.mqtt_ctx, &sub_info, 1, packet_id); 458 | } else { 459 | status = MQTT_Unsubscribe(&g_ps.mqtt_ctx, &sub_info, 1, packet_id); 460 | } 461 | if (status != MQTTSuccess) { 462 | LOGE("MQTT_Subscribe failed: Status=%s.", MQTT_Status_strerror(status)); 463 | return -1; 464 | } 465 | 466 | status = MQTT_ProcessLoop(&g_ps.mqtt_ctx); 467 | 468 | if (status != MQTTSuccess) { 469 | LOGE("MQTT_ProcessLoop failed: Status=%s.", MQTT_Status_strerror(status)); 470 | return -1; 471 | } 472 | 473 | return 0; 474 | } 475 | 476 | static void peer_signaling_onicecandidate(char* description, void* userdata) { 477 | cJSON* res; 478 | char* payload; 479 | if (g_ps.id > 0) { 480 | res = cJSON_CreateObject(); 481 | cJSON_AddStringToObject(res, "jsonrpc", RPC_VERSION); 482 | cJSON_AddNumberToObject(res, "id", g_ps.id); 483 | cJSON_AddStringToObject(res, "result", description); 484 | payload = cJSON_PrintUnformatted(res); 485 | if (payload) { 486 | peer_signaling_mqtt_publish(&g_ps.mqtt_ctx, payload); 487 | free(payload); 488 | } 489 | cJSON_Delete(res); 490 | g_ps.id = 0; 491 | } else { 492 | if (strlen(g_ps.token) > 0) { 493 | char cred[TOKEN_MAX_LEN + 10]; 494 | memset(cred, 0, sizeof(cred)); 495 | snprintf(cred, sizeof(cred), "Bearer %s", g_ps.token); 496 | peer_signaling_http_post(g_ps.host, g_ps.path, g_ps.port, cred, description); 497 | } else { 498 | peer_signaling_http_post(g_ps.host, g_ps.path, g_ps.port, "", description); 499 | } 500 | } 501 | } 502 | 503 | int peer_signaling_connect(const char* url, const char* token, PeerConnection* pc) { 504 | char* client_id; 505 | 506 | if ((g_ps.proto = peer_signaling_resolve_url(url, g_ps.host, &g_ps.port, g_ps.path)) < 0) { 507 | LOGE("Resolve URL failed"); 508 | } 509 | 510 | if (token && strlen(token) > 0) { 511 | strncpy(g_ps.token, token, sizeof(g_ps.token)); 512 | } 513 | 514 | g_ps.pc = pc; 515 | peer_connection_onicecandidate(g_ps.pc, peer_signaling_onicecandidate); 516 | 517 | switch (g_ps.proto) { 518 | case 0: { // MQTT 519 | client_id = strrchr(g_ps.path, '/'); 520 | snprintf(g_ps.client_id, sizeof(g_ps.client_id), "%s", client_id + 1); 521 | snprintf(g_ps.subtopic, sizeof(g_ps.subtopic), "%s/invoke", g_ps.path); 522 | snprintf(g_ps.pubtopic, sizeof(g_ps.pubtopic), "%s/result", g_ps.path); 523 | if (peer_signaling_mqtt_connect(g_ps.host, g_ps.port) == 0) { 524 | peer_signaling_mqtt_subscribe(1); 525 | } 526 | } break; 527 | case 1: { // HTTP 528 | peer_connection_create_offer(g_ps.pc); 529 | } break; 530 | default: { 531 | } break; 532 | } 533 | 534 | return 0; 535 | } 536 | 537 | void peer_signaling_disconnect() { 538 | MQTTStatus_t status = MQTTSuccess; 539 | 540 | if (!g_ps.proto && !peer_signaling_mqtt_subscribe(0)) { 541 | status = MQTT_Disconnect(&g_ps.mqtt_ctx); 542 | if (status != MQTTSuccess) { 543 | LOGE("Failed to disconnect with broker: %s", MQTT_Status_strerror(status)); 544 | } 545 | 546 | ssl_transport_disconnect(&g_ps.net_ctx); 547 | } 548 | LOGI("Disconnected"); 549 | } 550 | 551 | int peer_signaling_loop() { 552 | MQTT_ProcessLoop(&g_ps.mqtt_ctx); 553 | return 0; 554 | } 555 | #endif // DISABLE_PEER_SIGNALING 556 | -------------------------------------------------------------------------------- /src/peer_signaling.h: -------------------------------------------------------------------------------- 1 | #ifndef PEER_SIGNALING_H_ 2 | #define PEER_SIGNALING_H_ 3 | 4 | #include "peer_connection.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #ifndef DISABLE_PEER_SIGNALING 11 | 12 | int peer_signaling_connect(const char* url, const char* token, PeerConnection* pc); 13 | 14 | void peer_signaling_disconnect(); 15 | 16 | int peer_signaling_loop(); 17 | 18 | #endif // DISABLE_PEER_SIGNALING 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif // PEER_SIGNALING_H_ 25 | -------------------------------------------------------------------------------- /src/ports.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "config.h" 8 | 9 | #if CONFIG_USE_LWIP 10 | #include "lwip/ip_addr.h" 11 | #include "lwip/netdb.h" 12 | #include "lwip/netif.h" 13 | #include "lwip/sys.h" 14 | #else 15 | #include 16 | #include 17 | #include 18 | #include 19 | #endif 20 | 21 | #include "ports.h" 22 | #include "utils.h" 23 | 24 | int ports_get_host_addr(Address* addr, const char* iface_prefix) { 25 | int ret = 0; 26 | 27 | #if CONFIG_USE_LWIP 28 | struct netif* netif; 29 | int i; 30 | for (netif = netif_list; netif != NULL; netif = netif->next) { 31 | switch (addr->family) { 32 | case AF_INET6: 33 | for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { 34 | if (!ip6_addr_isany(netif_ip6_addr(netif, i))) { 35 | memcpy(&addr->sin6.sin6_addr, netif_ip6_addr(netif, i), 16); 36 | ret = 1; 37 | break; 38 | } 39 | } 40 | break; 41 | case AF_INET: 42 | default: 43 | if (!ip_addr_isany(&netif->ip_addr)) { 44 | memcpy(&addr->sin.sin_addr, &netif->ip_addr.u_addr.ip4, 4); 45 | ret = 1; 46 | } 47 | break; 48 | } 49 | 50 | if (ret) { 51 | break; 52 | } 53 | } 54 | #else 55 | 56 | struct ifaddrs *ifaddr, *ifa; 57 | 58 | if (getifaddrs(&ifaddr) == -1) { 59 | LOGE("getifaddrs failed: %s", strerror(errno)); 60 | return -1; 61 | } 62 | 63 | for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { 64 | if (ifa->ifa_addr == NULL) { 65 | continue; 66 | } 67 | 68 | if (ifa->ifa_addr->sa_family != addr->family) { 69 | continue; 70 | } 71 | 72 | if (iface_prefix && strlen(iface_prefix) > 0) { 73 | if (strncmp(ifa->ifa_name, iface_prefix, strlen(iface_prefix)) != 0) { 74 | continue; 75 | } 76 | 77 | } else { 78 | if ((ifa->ifa_flags & IFF_UP) == 0) { 79 | continue; 80 | } 81 | 82 | if ((ifa->ifa_flags & IFF_RUNNING) == 0) { 83 | continue; 84 | } 85 | 86 | if ((ifa->ifa_flags & IFF_LOOPBACK) == IFF_LOOPBACK) { 87 | continue; 88 | } 89 | } 90 | 91 | switch (ifa->ifa_addr->sa_family) { 92 | case AF_INET6: 93 | memcpy(&addr->sin6, ifa->ifa_addr, sizeof(struct sockaddr_in6)); 94 | break; 95 | case AF_INET: 96 | default: 97 | memcpy(&addr->sin, ifa->ifa_addr, sizeof(struct sockaddr_in)); 98 | break; 99 | } 100 | ret = 1; 101 | break; 102 | } 103 | freeifaddrs(ifaddr); 104 | #endif 105 | return ret; 106 | } 107 | 108 | int ports_resolve_addr(const char* host, Address* addr) { 109 | char addr_string[ADDRSTRLEN]; 110 | int ret = -1; 111 | struct addrinfo hints, *res, *p; 112 | int status; 113 | memset(&hints, 0, sizeof(hints)); 114 | hints.ai_family = AF_UNSPEC; 115 | hints.ai_socktype = SOCK_STREAM; 116 | 117 | if ((status = getaddrinfo(host, NULL, &hints, &res)) != 0) { 118 | LOGE("getaddrinfo error: %d\n", status); 119 | return ret; 120 | } 121 | 122 | // TODO: Support for IPv6 123 | addr_set_family(addr, AF_INET); 124 | for (p = res; p != NULL; p = p->ai_next) { 125 | if (p->ai_family == addr->family) { 126 | switch (addr->family) { 127 | case AF_INET6: 128 | memcpy(&addr->sin6, p->ai_addr, sizeof(struct sockaddr_in6)); 129 | break; 130 | case AF_INET: 131 | default: 132 | memcpy(&addr->sin, p->ai_addr, sizeof(struct sockaddr_in)); 133 | break; 134 | } 135 | ret = 0; 136 | } 137 | } 138 | 139 | addr_to_string(addr, addr_string, sizeof(addr_string)); 140 | LOGI("Resolved %s -> %s", host, addr_string); 141 | freeaddrinfo(res); 142 | return ret; 143 | } 144 | 145 | uint32_t ports_get_epoch_time() { 146 | struct timeval tv; 147 | gettimeofday(&tv, NULL); 148 | return (uint32_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; 149 | } 150 | 151 | void ports_sleep_ms(int ms) { 152 | #if CONFIG_USE_LWIP 153 | sys_msleep(ms); 154 | #else 155 | usleep(ms * 1000); 156 | #endif 157 | } 158 | -------------------------------------------------------------------------------- /src/ports.h: -------------------------------------------------------------------------------- 1 | #ifndef PORTS_H_ 2 | #define PORTS_H_ 3 | 4 | #include 5 | #include "address.h" 6 | 7 | int ports_resolve_addr(const char* host, Address* addr); 8 | 9 | int ports_resolve_mdns_host(const char* host, Address* addr); 10 | 11 | int ports_get_host_addr(Address* addr, const char* iface_prefix); 12 | 13 | uint32_t ports_get_epoch_time(); 14 | 15 | void ports_sleep_ms(int ms); 16 | 17 | #endif // PORTS_H_ 18 | -------------------------------------------------------------------------------- /src/rtcp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "address.h" 5 | #include "rtcp.h" 6 | #include "rtp.h" 7 | 8 | int rtcp_probe(uint8_t* packet, size_t size) { 9 | if (size < 8) 10 | return -1; 11 | 12 | RtpHeader* header = (RtpHeader*)packet; 13 | return ((header->type >= 64) && (header->type < 96)); 14 | } 15 | 16 | int rtcp_get_pli(uint8_t* packet, int len, uint32_t ssrc) { 17 | if (packet == NULL || len != 12) 18 | return -1; 19 | 20 | memset(packet, 0, len); 21 | RtcpHeader* rtcp_header = (RtcpHeader*)packet; 22 | rtcp_header->version = 2; 23 | rtcp_header->type = RTCP_PSFB; 24 | rtcp_header->rc = 1; 25 | rtcp_header->length = htons((len / 4) - 1); 26 | memcpy(packet + 8, &ssrc, 4); 27 | 28 | return 12; 29 | } 30 | 31 | int rtcp_get_fir(uint8_t* packet, int len, int* seqnr) { 32 | if (packet == NULL || len != 20 || seqnr == NULL) 33 | return -1; 34 | 35 | memset(packet, 0, len); 36 | RtcpHeader* rtcp = (RtcpHeader*)packet; 37 | *seqnr = *seqnr + 1; 38 | if (*seqnr < 0 || *seqnr >= 256) 39 | *seqnr = 0; 40 | 41 | rtcp->version = 2; 42 | rtcp->type = RTCP_PSFB; 43 | rtcp->rc = 4; 44 | rtcp->length = htons((len / 4) - 1); 45 | RtcpFb* rtcp_fb = (RtcpFb*)rtcp; 46 | RtcpFir* fir = (RtcpFir*)rtcp_fb->fci; 47 | fir->seqnr = htonl(*seqnr << 24); 48 | 49 | return 20; 50 | } 51 | 52 | RtcpRr rtcp_parse_rr(uint8_t* packet) { 53 | RtcpRr rtcp_rr; 54 | memcpy(&rtcp_rr.header, packet, sizeof(rtcp_rr.header)); 55 | memcpy(&rtcp_rr.report_block[0], packet + 8, 6 * sizeof(uint32_t)); 56 | 57 | return rtcp_rr; 58 | } 59 | -------------------------------------------------------------------------------- /src/rtcp.h: -------------------------------------------------------------------------------- 1 | #ifndef RTCP_H_ 2 | #define RTCP_H_ 3 | 4 | #ifdef __BYTE_ORDER 5 | #define __BIG_ENDIAN 4321 6 | #define __LITTLE_ENDIAN 1234 7 | #elif __APPLE__ 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | typedef enum RtcpType { 14 | 15 | RTCP_FIR = 192, 16 | RTCP_SR = 200, 17 | RTCP_RR = 201, 18 | RTCP_SDES = 202, 19 | RTCP_BYE = 203, 20 | RTCP_APP = 204, 21 | RTCP_RTPFB = 205, 22 | RTCP_PSFB = 206, 23 | RTCP_XR = 207, 24 | 25 | } RtcpType; 26 | 27 | typedef struct RtcpHeader { 28 | #if __BYTE_ORDER == __BIG_ENDIAN 29 | uint16_t version : 2; 30 | uint16_t padding : 1; 31 | uint16_t rc : 5; 32 | uint16_t type : 8; 33 | #elif __BYTE_ORDER == __LITTLE_ENDIAN 34 | uint16_t rc : 5; 35 | uint16_t padding : 1; 36 | uint16_t version : 2; 37 | uint16_t type : 8; 38 | #endif 39 | uint16_t length : 16; 40 | 41 | } RtcpHeader; 42 | 43 | typedef struct RtcpReportBlock { 44 | uint32_t ssrc; 45 | uint32_t flcnpl; 46 | uint32_t ehsnr; 47 | uint32_t jitter; 48 | uint32_t lsr; 49 | uint32_t dlsr; 50 | 51 | } RtcpReportBlock; 52 | 53 | typedef struct RtcpRr { 54 | RtcpHeader header; 55 | uint32_t ssrc; 56 | RtcpReportBlock report_block[1]; 57 | 58 | } RtcpRr; 59 | 60 | typedef struct RtcpFir { 61 | uint32_t ssrc; 62 | uint32_t seqnr; 63 | 64 | } RtcpFir; 65 | 66 | typedef struct RtcpFb { 67 | RtcpHeader header; 68 | uint32_t ssrc; 69 | uint32_t media; 70 | char fci[1]; 71 | 72 | } RtcpFb; 73 | 74 | int rtcp_probe(uint8_t* packet, size_t size); 75 | 76 | int rtcp_get_pli(uint8_t* packet, int len, uint32_t ssrc); 77 | 78 | int rtcp_get_fir(uint8_t* packet, int len, int* seqnr); 79 | 80 | RtcpRr rtcp_parse_rr(uint8_t* packet); 81 | 82 | #endif // RTCP_H_ 83 | -------------------------------------------------------------------------------- /src/rtp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "address.h" 5 | #include "peer_connection.h" 6 | #include "rtp.h" 7 | #include "utils.h" 8 | 9 | typedef enum RtpH264Type { 10 | 11 | NALU = 23, 12 | FU_A = 28, 13 | 14 | } RtpH264Type; 15 | 16 | typedef struct NaluHeader { 17 | uint8_t type : 5; 18 | uint8_t nri : 2; 19 | uint8_t f : 1; 20 | } NaluHeader; 21 | 22 | typedef struct FuHeader { 23 | uint8_t type : 5; 24 | uint8_t r : 1; 25 | uint8_t e : 1; 26 | uint8_t s : 1; 27 | } FuHeader; 28 | 29 | #define RTP_PAYLOAD_SIZE (CONFIG_MTU - sizeof(RtpHeader)) 30 | #define FU_PAYLOAD_SIZE (CONFIG_MTU - sizeof(RtpHeader) - sizeof(FuHeader) - sizeof(NaluHeader)) 31 | 32 | int rtp_packet_validate(uint8_t* packet, size_t size) { 33 | if (size < 12) 34 | return 0; 35 | 36 | RtpHeader* rtp_header = (RtpHeader*)packet; 37 | return ((rtp_header->type < 64) || (rtp_header->type >= 96)); 38 | } 39 | 40 | uint32_t rtp_get_ssrc(uint8_t* packet) { 41 | RtpHeader* rtp_header = (RtpHeader*)packet; 42 | return ntohl(rtp_header->ssrc); 43 | } 44 | 45 | static int rtp_encoder_encode_h264_single(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { 46 | RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; 47 | 48 | rtp_packet->header.version = 2; 49 | rtp_packet->header.padding = 0; 50 | rtp_packet->header.extension = 0; 51 | rtp_packet->header.csrccount = 0; 52 | rtp_packet->header.markerbit = 0; 53 | rtp_packet->header.type = rtp_encoder->type; 54 | rtp_packet->header.seq_number = htons(rtp_encoder->seq_number++); 55 | rtp_packet->header.timestamp = htonl(rtp_encoder->timestamp); 56 | rtp_packet->header.ssrc = htonl(rtp_encoder->ssrc); 57 | 58 | // I frame and P frame 59 | if ((*buf & 0x1f) == 0x05 || (*buf & 0x1f) == 0x01) { 60 | rtp_packet->header.markerbit = 1; 61 | rtp_encoder->timestamp += rtp_encoder->timestamp_increment; 62 | } 63 | #if 0 64 | LOGI("markbit: %d, timestamp: %d, nalu type: %d", rtp_packet->header.markerbit, rtp_encoder->timestamp, buf[0] & 0x1f); 65 | #endif 66 | 67 | memcpy(rtp_packet->payload, buf, size); 68 | rtp_encoder->on_packet(rtp_encoder->buf, size + sizeof(RtpHeader), rtp_encoder->user_data); 69 | return 0; 70 | } 71 | 72 | static int rtp_encoder_encode_h264_fu_a(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { 73 | RtpPacket* rtp_packet = (RtpPacket*)rtp_encoder->buf; 74 | 75 | rtp_packet->header.version = 2; 76 | rtp_packet->header.padding = 0; 77 | rtp_packet->header.extension = 0; 78 | rtp_packet->header.csrccount = 0; 79 | rtp_packet->header.markerbit = 0; 80 | rtp_packet->header.type = rtp_encoder->type; 81 | rtp_packet->header.timestamp = htonl(rtp_encoder->timestamp); 82 | rtp_packet->header.ssrc = htonl(rtp_encoder->ssrc); 83 | uint8_t type = buf[0] & 0x1f; 84 | uint8_t nri = (buf[0] & 0x60) >> 5; 85 | buf = buf + 1; 86 | size = size - 1; 87 | 88 | // increase timestamp if I, P frame 89 | if (type == 0x05 || type == 0x01) { 90 | rtp_encoder->timestamp += rtp_encoder->timestamp_increment; 91 | } 92 | 93 | NaluHeader* fu_indicator = (NaluHeader*)rtp_packet->payload; 94 | FuHeader* fu_header = (FuHeader*)rtp_packet->payload + sizeof(NaluHeader); 95 | fu_header->s = 1; 96 | 97 | while (size > 0) { 98 | fu_indicator->type = FU_A; 99 | fu_indicator->nri = nri; 100 | fu_indicator->f = 0; 101 | fu_header->type = type; 102 | fu_header->r = 0; 103 | rtp_packet->header.seq_number = htons(rtp_encoder->seq_number++); 104 | 105 | if (size <= FU_PAYLOAD_SIZE) { 106 | fu_header->e = 1; 107 | rtp_packet->header.markerbit = 1; 108 | memcpy(rtp_packet->payload + sizeof(NaluHeader) + sizeof(FuHeader), buf, size); 109 | rtp_encoder->on_packet(rtp_encoder->buf, size + sizeof(RtpHeader) + sizeof(NaluHeader) + sizeof(FuHeader), rtp_encoder->user_data); 110 | break; 111 | } 112 | 113 | fu_header->e = 0; 114 | 115 | memcpy(rtp_packet->payload + sizeof(NaluHeader) + sizeof(FuHeader), buf, FU_PAYLOAD_SIZE); 116 | rtp_encoder->on_packet(rtp_encoder->buf, CONFIG_MTU, rtp_encoder->user_data); 117 | size -= FU_PAYLOAD_SIZE; 118 | buf += FU_PAYLOAD_SIZE; 119 | 120 | fu_header->s = 0; 121 | } 122 | return 0; 123 | } 124 | 125 | static uint8_t* h264_find_nalu(uint8_t* buf_start, uint8_t* buf_end) { 126 | uint8_t* p = buf_start + 2; 127 | 128 | while (p < buf_end) { 129 | if (*(p - 2) == 0x00 && *(p - 1) == 0x00 && *p == 0x01) 130 | return p + 1; 131 | p++; 132 | } 133 | 134 | return buf_end; 135 | } 136 | 137 | static int rtp_encoder_encode_h264(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { 138 | uint8_t* buf_end = buf + size; 139 | uint8_t *pstart, *pend; 140 | size_t nalu_size; 141 | 142 | for (pstart = h264_find_nalu(buf, buf_end); pstart < buf_end; pstart = pend) { 143 | pend = h264_find_nalu(pstart, buf_end); 144 | nalu_size = pend - pstart; 145 | 146 | if (pend != buf_end) 147 | nalu_size--; 148 | 149 | while (pstart[nalu_size - 1] == 0x00) 150 | nalu_size--; 151 | 152 | if (nalu_size <= RTP_PAYLOAD_SIZE) { 153 | rtp_encoder_encode_h264_single(rtp_encoder, pstart, nalu_size); 154 | 155 | } else { 156 | rtp_encoder_encode_h264_fu_a(rtp_encoder, pstart, nalu_size); 157 | } 158 | } 159 | 160 | return 0; 161 | } 162 | 163 | static int rtp_encoder_encode_generic(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { 164 | RtpHeader* rtp_header = (RtpHeader*)rtp_encoder->buf; 165 | rtp_header->version = 2; 166 | rtp_header->padding = 0; 167 | rtp_header->extension = 0; 168 | rtp_header->csrccount = 0; 169 | rtp_header->markerbit = 0; 170 | rtp_header->type = rtp_encoder->type; 171 | rtp_header->seq_number = htons(rtp_encoder->seq_number++); 172 | rtp_header->timestamp = htonl(rtp_encoder->timestamp); 173 | rtp_encoder->timestamp += rtp_encoder->timestamp_increment; 174 | rtp_header->ssrc = htonl(rtp_encoder->ssrc); 175 | memcpy(rtp_encoder->buf + sizeof(RtpHeader), buf, size); 176 | 177 | rtp_encoder->on_packet(rtp_encoder->buf, size + sizeof(RtpHeader), rtp_encoder->user_data); 178 | 179 | return 0; 180 | } 181 | 182 | void rtp_encoder_init(RtpEncoder* rtp_encoder, MediaCodec codec, RtpOnPacket on_packet, void* user_data) { 183 | rtp_encoder->on_packet = on_packet; 184 | rtp_encoder->user_data = user_data; 185 | rtp_encoder->timestamp = 0; 186 | rtp_encoder->seq_number = 0; 187 | 188 | switch (codec) { 189 | case CODEC_H264: 190 | rtp_encoder->type = PT_H264; 191 | rtp_encoder->ssrc = SSRC_H264; 192 | rtp_encoder->timestamp_increment = 90000 / 30; // 30 FPS. 193 | rtp_encoder->encode_func = rtp_encoder_encode_h264; 194 | break; 195 | case CODEC_PCMA: 196 | rtp_encoder->type = PT_PCMA; 197 | rtp_encoder->ssrc = SSRC_PCMA; 198 | rtp_encoder->timestamp_increment = CONFIG_AUDIO_DURATION * 8000 / 1000; 199 | rtp_encoder->encode_func = rtp_encoder_encode_generic; 200 | break; 201 | case CODEC_PCMU: 202 | rtp_encoder->type = PT_PCMU; 203 | rtp_encoder->ssrc = SSRC_PCMU; 204 | rtp_encoder->timestamp_increment = CONFIG_AUDIO_DURATION * 8000 / 1000; 205 | rtp_encoder->encode_func = rtp_encoder_encode_generic; 206 | break; 207 | case CODEC_OPUS: 208 | rtp_encoder->type = PT_OPUS; 209 | rtp_encoder->ssrc = SSRC_OPUS; 210 | rtp_encoder->timestamp_increment = CONFIG_AUDIO_DURATION * 48000 / 1000; 211 | rtp_encoder->encode_func = rtp_encoder_encode_generic; 212 | break; 213 | default: 214 | break; 215 | } 216 | } 217 | 218 | int rtp_encoder_encode(RtpEncoder* rtp_encoder, uint8_t* buf, size_t size) { 219 | return rtp_encoder->encode_func(rtp_encoder, buf, size); 220 | } 221 | 222 | static int rtp_decode_generic(RtpDecoder* rtp_decoder, uint8_t* buf, size_t size) { 223 | RtpPacket* rtp_packet = (RtpPacket*)buf; 224 | if (rtp_decoder->on_packet != NULL) 225 | rtp_decoder->on_packet(rtp_packet->payload, size - sizeof(RtpHeader), rtp_decoder->user_data); 226 | // even if there is no callback set, assume everything is ok for caller and do not return an error 227 | return (int)size; 228 | } 229 | 230 | void rtp_decoder_init(RtpDecoder* rtp_decoder, MediaCodec codec, RtpOnPacket on_packet, void* user_data) { 231 | rtp_decoder->on_packet = on_packet; 232 | rtp_decoder->user_data = user_data; 233 | 234 | switch (codec) { 235 | case CODEC_H264: 236 | // TODO: implement 237 | rtp_decoder->decode_func = NULL; 238 | break; 239 | case CODEC_PCMA: 240 | case CODEC_PCMU: 241 | case CODEC_OPUS: 242 | rtp_decoder->decode_func = rtp_decode_generic; 243 | default: 244 | break; 245 | } 246 | } 247 | 248 | int rtp_decoder_decode(RtpDecoder* rtp_decoder, uint8_t* buf, size_t size) { 249 | if (rtp_decoder->decode_func == NULL) 250 | return -1; 251 | return rtp_decoder->decode_func(rtp_decoder, buf, size); 252 | } 253 | -------------------------------------------------------------------------------- /src/rtp.h: -------------------------------------------------------------------------------- 1 | #ifndef RTP_H_ 2 | #define RTP_H_ 3 | 4 | #include 5 | 6 | #ifdef __BYTE_ORDER 7 | #define __BIG_ENDIAN 4321 8 | #define __LITTLE_ENDIAN 1234 9 | #elif __APPLE__ 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | #include "config.h" 16 | #include "peer_connection.h" 17 | 18 | typedef enum RtpPayloadType { 19 | 20 | PT_PCMU = 0, 21 | PT_PCMA = 8, 22 | PT_G722 = 9, 23 | PT_H264 = 96, 24 | PT_OPUS = 111 25 | 26 | } RtpPayloadType; 27 | 28 | typedef enum RtpSsrc { 29 | 30 | SSRC_H264 = 1, 31 | SSRC_PCMA = 4, 32 | SSRC_PCMU = 5, 33 | SSRC_OPUS = 6, 34 | 35 | } RtpSsrc; 36 | 37 | typedef struct RtpHeader { 38 | #if __BYTE_ORDER == __BIG_ENDIAN 39 | uint16_t version : 2; 40 | uint16_t padding : 1; 41 | uint16_t extension : 1; 42 | uint16_t csrccount : 4; 43 | uint16_t markerbit : 1; 44 | uint16_t type : 7; 45 | #elif __BYTE_ORDER == __LITTLE_ENDIAN 46 | uint16_t csrccount : 4; 47 | uint16_t extension : 1; 48 | uint16_t padding : 1; 49 | uint16_t version : 2; 50 | uint16_t type : 7; 51 | uint16_t markerbit : 1; 52 | #endif 53 | uint16_t seq_number; 54 | uint32_t timestamp; 55 | uint32_t ssrc; 56 | uint32_t csrc[0]; 57 | 58 | } RtpHeader; 59 | 60 | typedef struct RtpPacket { 61 | RtpHeader header; 62 | uint8_t payload[0]; 63 | 64 | } RtpPacket; 65 | 66 | typedef struct RtpMap { 67 | int pt_h264; 68 | int pt_opus; 69 | int pt_pcma; 70 | 71 | } RtpMap; 72 | 73 | typedef struct RtpEncoder RtpEncoder; 74 | typedef struct RtpDecoder RtpDecoder; 75 | typedef void (*RtpOnPacket)(uint8_t* packet, size_t bytes, void* user_data); 76 | 77 | struct RtpDecoder { 78 | RtpPayloadType type; 79 | RtpOnPacket on_packet; 80 | int (*decode_func)(RtpDecoder* rtp_decoder, uint8_t* data, size_t size); 81 | void* user_data; 82 | }; 83 | 84 | struct RtpEncoder { 85 | RtpPayloadType type; 86 | RtpOnPacket on_packet; 87 | int (*encode_func)(RtpEncoder* rtp_encoder, uint8_t* data, size_t size); 88 | void* user_data; 89 | uint16_t seq_number; 90 | uint32_t ssrc; 91 | uint32_t timestamp; 92 | uint32_t timestamp_increment; 93 | uint8_t buf[CONFIG_MTU + 128]; 94 | }; 95 | 96 | int rtp_packet_validate(uint8_t* packet, size_t size); 97 | 98 | void rtp_encoder_init(RtpEncoder* rtp_encoder, MediaCodec codec, RtpOnPacket on_packet, void* user_data); 99 | 100 | int rtp_encoder_encode(RtpEncoder* rtp_encoder, uint8_t* data, size_t size); 101 | 102 | void rtp_decoder_init(RtpDecoder* rtp_decoder, MediaCodec codec, RtpOnPacket on_packet, void* user_data); 103 | 104 | int rtp_decoder_decode(RtpDecoder* rtp_decoder, uint8_t* data, size_t size); 105 | 106 | uint32_t rtp_get_ssrc(uint8_t* packet); 107 | 108 | #endif // RTP_H_ 109 | -------------------------------------------------------------------------------- /src/sctp.h: -------------------------------------------------------------------------------- 1 | #ifndef SCTP_H_ 2 | #define SCTP_H_ 3 | 4 | #include "buffer.h" 5 | #include "config.h" 6 | #include "dtls_srtp.h" 7 | #include "utils.h" 8 | 9 | typedef enum DecpMsgType { 10 | 11 | DATA_CHANNEL_OPEN = 0x03, 12 | DATA_CHANNEL_ACK = 0x02, 13 | 14 | } DecpMsgType; 15 | 16 | typedef enum DataChannelPpid { 17 | 18 | DATA_CHANNEL_PPID_CONTROL = 50, 19 | DATA_CHANNEL_PPID_DOMSTRING = 51, 20 | DATA_CHANNEL_PPID_BINARY_PARTIAL = 52, 21 | DATA_CHANNEL_PPID_BINARY = 53, 22 | DATA_CHANNEL_PPID_DOMSTRING_PARTIAL = 54 23 | 24 | } DataChannelPpid; 25 | 26 | #if !CONFIG_USE_USRSCTP 27 | 28 | typedef struct SctpChunkParam { 29 | uint16_t type; 30 | uint16_t length; 31 | uint8_t value[0]; 32 | 33 | } SctpChunkParam; 34 | 35 | typedef enum SctpParamType { 36 | 37 | SCTP_PARAM_STATE_COOKIE = 7, 38 | 39 | } SctpParamType; 40 | 41 | typedef enum SctpHeaderType { 42 | 43 | SCTP_DATA = 0, 44 | SCTP_INIT = 1, 45 | SCTP_INIT_ACK = 2, 46 | SCTP_SACK = 3, 47 | SCTP_HEARTBEAT = 4, 48 | SCTP_HEARTBEAT_ACK = 5, 49 | SCTP_ABORT = 6, 50 | SCTP_SHUTDOWN = 7, 51 | SCTP_SHUTDOWN_ACK = 8, 52 | SCTP_ERROR = 9, 53 | SCTP_COOKIE_ECHO = 10, 54 | SCTP_COOKIE_ACK = 11, 55 | SCTP_ECNE = 12, 56 | SCTP_CWR = 13, 57 | SCTP_SHUTDOWN_COMPLETE = 14, 58 | SCTP_AUTH = 15, 59 | SCTP_ASCONF_ACK = 128, 60 | SCTP_ASCONF = 130, 61 | SCTP_FORWARD_TSN = 192 62 | 63 | } SctpHeaderType; 64 | 65 | typedef struct SctpChunkCommon { 66 | uint8_t type; 67 | uint8_t flags; 68 | uint16_t length; 69 | 70 | } SctpChunkCommon; 71 | 72 | typedef struct SctpForwardTsnChunk { 73 | SctpChunkCommon common; 74 | uint32_t new_cumulative_tsn; 75 | uint16_t stream_number; 76 | uint16_t stream_sequence_number; 77 | 78 | } SctpForwardTsnChunk; 79 | 80 | typedef struct SctpHeader { 81 | uint16_t source_port; 82 | uint16_t destination_port; 83 | uint32_t verification_tag; 84 | uint32_t checksum; 85 | 86 | } SctpHeader; 87 | 88 | typedef struct SctpPacket { 89 | SctpHeader header; 90 | uint8_t chunks[0]; 91 | 92 | } SctpPacket; 93 | 94 | typedef struct SctpSackChunk { 95 | SctpChunkCommon common; 96 | uint32_t cumulative_tsn_ack; 97 | uint32_t a_rwnd; 98 | uint16_t number_of_gap_ack_blocks; 99 | uint16_t number_of_dup_tsns; 100 | uint8_t blocks[0]; 101 | 102 | } SctpSackChunk; 103 | 104 | typedef struct SctpDataChunk { 105 | uint8_t type; 106 | uint8_t iube; 107 | uint16_t length; 108 | uint32_t tsn; 109 | uint16_t sid; 110 | uint16_t sqn; 111 | uint32_t ppid; 112 | uint8_t data[0]; 113 | 114 | } SctpDataChunk; 115 | 116 | typedef struct SctpInitChunk { 117 | SctpChunkCommon common; 118 | uint32_t initiate_tag; 119 | uint32_t a_rwnd; 120 | uint16_t number_of_outbound_streams; 121 | uint16_t number_of_inbound_streams; 122 | uint32_t initial_tsn; 123 | SctpChunkParam param[0]; 124 | 125 | } SctpInitChunk; 126 | 127 | typedef struct SctpCookieEchoChunk { 128 | SctpChunkCommon common; 129 | uint8_t cookie[0]; 130 | } SctpCookieEchoChunk; 131 | 132 | #endif 133 | 134 | typedef enum SctpDataPpid { 135 | 136 | PPID_CONTROL = 50, 137 | PPID_STRING = 51, 138 | PPID_BINARY = 53, 139 | PPID_STRING_EMPTY = 56, 140 | PPID_BINARY_EMPTY = 57 141 | 142 | } SctpDataPpid; 143 | 144 | #define SCTP_MAX_STREAMS 5 145 | 146 | typedef struct { 147 | char label[32]; // Stream label 148 | uint16_t sid; // Stream ID 149 | } SctpStreamEntry; 150 | 151 | typedef struct Sctp { 152 | struct socket* sock; 153 | 154 | int local_port; 155 | int remote_port; 156 | int connected; 157 | uint32_t verification_tag; 158 | uint32_t tsn; 159 | DtlsSrtp* dtls_srtp; 160 | Buffer** data_rb; 161 | int stream_count; 162 | SctpStreamEntry stream_table[SCTP_MAX_STREAMS]; 163 | 164 | /* datachannel */ 165 | void (*onmessage)(char* msg, size_t len, void* userdata, uint16_t sid); 166 | void (*onopen)(void* userdata); 167 | void (*onclose)(void* userdata); 168 | 169 | void* userdata; 170 | uint8_t buf[CONFIG_MTU]; 171 | } Sctp; 172 | 173 | int sctp_create_association(Sctp* sctp, DtlsSrtp* dtls_srtp); 174 | 175 | void sctp_destroy_association(Sctp* sctp); 176 | 177 | int sctp_is_connected(Sctp* sctp); 178 | 179 | void sctp_incoming_data(Sctp* sctp, char* buf, size_t len); 180 | 181 | int sctp_outgoing_data(Sctp* sctp, char* buf, size_t len, SctpDataPpid ppid, uint16_t sid); 182 | 183 | void sctp_onmessage(Sctp* sctp, void (*onmessage)(char* msg, size_t len, void* userdata, uint16_t sid)); 184 | 185 | void sctp_onopen(Sctp* sctp, void (*onopen)(void* userdata)); 186 | 187 | void sctp_onclose(Sctp* sctp, void (*onclose)(void* userdata)); 188 | 189 | #endif // SCTP_H_ 190 | -------------------------------------------------------------------------------- /src/sdp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sdp.h" 5 | 6 | int sdp_append(Sdp* sdp, const char* format, ...) { 7 | va_list argptr; 8 | 9 | va_start(argptr, format); 10 | 11 | if (sdp->content[0] == '\0') { 12 | vsnprintf(sdp->content, sizeof(sdp->content), format, argptr); 13 | } else { 14 | vsnprintf(sdp->content + strlen(sdp->content), sizeof(sdp->content) - strlen(sdp->content), format, argptr); 15 | } 16 | 17 | if (sdp->content[strlen(sdp->content) - 1] != '\n') { 18 | strcat(sdp->content, "\r\n"); 19 | } 20 | 21 | va_end(argptr); 22 | return 0; 23 | } 24 | 25 | void sdp_reset(Sdp* sdp) { 26 | memset(sdp->content, 0, sizeof(sdp->content)); 27 | } 28 | 29 | void sdp_append_h264(Sdp* sdp) { 30 | sdp_append(sdp, "m=video 9 UDP/TLS/RTP/SAVPF 96 102"); 31 | sdp_append(sdp, "c=IN IP4 0.0.0.0"); 32 | sdp_append(sdp, "a=rtcp-fb:102 nack"); 33 | sdp_append(sdp, "a=rtcp-fb:102 nack pli"); 34 | sdp_append(sdp, "a=fmtp:96 profile-level-id=42e01f;level-asymmetry-allowed=1"); 35 | sdp_append(sdp, "a=fmtp:102 profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1"); 36 | sdp_append(sdp, "a=rtpmap:96 H264/90000"); 37 | sdp_append(sdp, "a=rtpmap:102 H264/90000"); 38 | sdp_append(sdp, "a=ssrc:1 cname:webrtc-h264"); 39 | sdp_append(sdp, "a=sendrecv"); 40 | sdp_append(sdp, "a=mid:video"); 41 | sdp_append(sdp, "a=rtcp-mux"); 42 | } 43 | 44 | void sdp_append_pcma(Sdp* sdp) { 45 | sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 8"); 46 | sdp_append(sdp, "c=IN IP4 0.0.0.0"); 47 | sdp_append(sdp, "a=rtpmap:8 PCMA/8000"); 48 | sdp_append(sdp, "a=ssrc:4 cname:webrtc-pcma"); 49 | sdp_append(sdp, "a=sendrecv"); 50 | sdp_append(sdp, "a=mid:audio"); 51 | sdp_append(sdp, "a=rtcp-mux"); 52 | } 53 | 54 | void sdp_append_pcmu(Sdp* sdp) { 55 | sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 0"); 56 | sdp_append(sdp, "c=IN IP4 0.0.0.0"); 57 | sdp_append(sdp, "a=rtpmap:0 PCMU/8000"); 58 | sdp_append(sdp, "a=ssrc:5 cname:webrtc-pcmu"); 59 | sdp_append(sdp, "a=sendrecv"); 60 | sdp_append(sdp, "a=mid:audio"); 61 | sdp_append(sdp, "a=rtcp-mux"); 62 | } 63 | 64 | void sdp_append_opus(Sdp* sdp) { 65 | sdp_append(sdp, "m=audio 9 UDP/TLS/RTP/SAVP 111"); 66 | sdp_append(sdp, "c=IN IP4 0.0.0.0"); 67 | sdp_append(sdp, "a=rtpmap:111 opus/48000/2"); 68 | sdp_append(sdp, "a=ssrc:6 cname:webrtc-opus"); 69 | sdp_append(sdp, "a=sendrecv"); 70 | sdp_append(sdp, "a=mid:audio"); 71 | sdp_append(sdp, "a=rtcp-mux"); 72 | } 73 | 74 | void sdp_append_datachannel(Sdp* sdp) { 75 | sdp_append(sdp, "m=application 50712 UDP/DTLS/SCTP webrtc-datachannel"); 76 | sdp_append(sdp, "c=IN IP4 0.0.0.0"); 77 | sdp_append(sdp, "a=mid:datachannel"); 78 | sdp_append(sdp, "a=sctp-port:5000"); 79 | sdp_append(sdp, "a=max-message-size:262144"); 80 | } 81 | 82 | void sdp_create(Sdp* sdp, int b_video, int b_audio, int b_datachannel) { 83 | char bundle[64]; 84 | sdp_append(sdp, "v=0"); 85 | sdp_append(sdp, "o=- 1495799811084970 1495799811084970 IN IP4 0.0.0.0"); 86 | sdp_append(sdp, "s=-"); 87 | sdp_append(sdp, "t=0 0"); 88 | sdp_append(sdp, "a=msid-semantic: iot"); 89 | #if ICE_LITE 90 | sdp_append(sdp, "a=ice-lite"); 91 | #endif 92 | memset(bundle, 0, sizeof(bundle)); 93 | 94 | strcat(bundle, "a=group:BUNDLE"); 95 | 96 | if (b_video) { 97 | strcat(bundle, " video"); 98 | } 99 | 100 | if (b_audio) { 101 | strcat(bundle, " audio"); 102 | } 103 | 104 | if (b_datachannel) { 105 | strcat(bundle, " datachannel"); 106 | } 107 | 108 | sdp_append(sdp, bundle); 109 | } 110 | -------------------------------------------------------------------------------- /src/sdp.h: -------------------------------------------------------------------------------- 1 | #ifndef SDP_H_ 2 | #define SDP_H_ 3 | 4 | #include 5 | #include "config.h" 6 | 7 | #define SDP_ATTR_LENGTH 128 8 | 9 | #ifndef ICE_LITE 10 | #define ICE_LITE 0 11 | #endif 12 | 13 | typedef struct Sdp { 14 | char content[CONFIG_SDP_BUFFER_SIZE]; 15 | 16 | } Sdp; 17 | 18 | void sdp_append_h264(Sdp* sdp); 19 | 20 | void sdp_append_pcma(Sdp* sdp); 21 | 22 | void sdp_append_pcmu(Sdp* sdp); 23 | 24 | void sdp_append_opus(Sdp* sdp); 25 | 26 | void sdp_append_datachannel(Sdp* sdp); 27 | 28 | void sdp_create(Sdp* sdp, int b_video, int b_audio, int b_datachannel); 29 | 30 | int sdp_append(Sdp* sdp, const char* format, ...); 31 | 32 | void sdp_reset(Sdp* sdp); 33 | 34 | #endif // SDP_H_ 35 | -------------------------------------------------------------------------------- /src/socket.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "socket.h" 6 | #include "utils.h" 7 | 8 | int udp_socket_add_multicast_group(UdpSocket* udp_socket, Address* mcast_addr) { 9 | int ret = 0; 10 | struct ip_mreq imreq = {0}; 11 | struct in_addr iaddr = {0}; 12 | 13 | imreq.imr_interface.s_addr = INADDR_ANY; 14 | // IPV4 only 15 | imreq.imr_multiaddr.s_addr = mcast_addr->sin.sin_addr.s_addr; 16 | 17 | if ((ret = setsockopt(udp_socket->fd, IPPROTO_IP, IP_MULTICAST_IF, &iaddr, sizeof(struct in_addr))) < 0) { 18 | LOGE("Failed to set IP_MULTICAST_IF: %d", ret); 19 | return ret; 20 | } 21 | 22 | if ((ret = setsockopt(udp_socket->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imreq, sizeof(struct ip_mreq))) < 0) { 23 | LOGE("Failed to set IP_ADD_MEMBERSHIP: %d", ret); 24 | return ret; 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | int udp_socket_open(UdpSocket* udp_socket, int family, int port) { 31 | int ret; 32 | int reuse = 1; 33 | struct sockaddr* sa; 34 | socklen_t sock_len; 35 | 36 | udp_socket->bind_addr.family = family; 37 | switch (family) { 38 | case AF_INET6: 39 | udp_socket->fd = socket(AF_INET6, SOCK_DGRAM, 0); 40 | udp_socket->bind_addr.sin6.sin6_family = AF_INET6; 41 | udp_socket->bind_addr.sin6.sin6_port = htons(port); 42 | udp_socket->bind_addr.sin6.sin6_addr = in6addr_any; 43 | udp_socket->bind_addr.port = ntohs(udp_socket->bind_addr.sin6.sin6_port); 44 | sa = (struct sockaddr*)&udp_socket->bind_addr.sin6; 45 | sock_len = sizeof(struct sockaddr_in6); 46 | break; 47 | case AF_INET: 48 | default: 49 | udp_socket->fd = socket(AF_INET, SOCK_DGRAM, 0); 50 | udp_socket->bind_addr.sin.sin_family = AF_INET; 51 | udp_socket->bind_addr.sin.sin_port = htons(port); 52 | udp_socket->bind_addr.sin.sin_addr.s_addr = htonl(INADDR_ANY); 53 | sa = (struct sockaddr*)&udp_socket->bind_addr.sin; 54 | sock_len = sizeof(struct sockaddr_in); 55 | break; 56 | } 57 | 58 | if (udp_socket->fd < 0) { 59 | LOGE("Failed to create socket"); 60 | return -1; 61 | } 62 | 63 | do { 64 | if ((ret = setsockopt(udp_socket->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) < 0) { 65 | LOGW("reuse failed. ignore"); 66 | } 67 | 68 | if ((ret = bind(udp_socket->fd, sa, sock_len)) < 0) { 69 | LOGE("Failed to bind socket: %d", ret); 70 | break; 71 | } 72 | 73 | if (getsockname(udp_socket->fd, sa, &sock_len) < 0) { 74 | LOGE("Get socket info failed"); 75 | break; 76 | } 77 | } while (0); 78 | 79 | if (ret < 0) { 80 | udp_socket_close(udp_socket); 81 | return -1; 82 | } 83 | 84 | switch (udp_socket->bind_addr.family) { 85 | case AF_INET6: 86 | udp_socket->bind_addr.port = ntohs(udp_socket->bind_addr.sin6.sin6_port); 87 | break; 88 | case AF_INET: 89 | default: 90 | udp_socket->bind_addr.port = ntohs(udp_socket->bind_addr.sin.sin_port); 91 | break; 92 | } 93 | 94 | return 0; 95 | } 96 | 97 | void udp_socket_close(UdpSocket* udp_socket) { 98 | if (udp_socket->fd > 0) { 99 | close(udp_socket->fd); 100 | } 101 | } 102 | 103 | int udp_socket_sendto(UdpSocket* udp_socket, Address* addr, const uint8_t* buf, int len) { 104 | struct sockaddr* sa; 105 | socklen_t sock_len; 106 | int ret = -1; 107 | 108 | if (udp_socket->fd < 0) { 109 | LOGE("sendto before socket init"); 110 | return -1; 111 | } 112 | 113 | switch (addr->family) { 114 | case AF_INET6: 115 | addr->sin6.sin6_family = AF_INET6; 116 | sa = (struct sockaddr*)&addr->sin6; 117 | sock_len = sizeof(struct sockaddr_in6); 118 | break; 119 | case AF_INET: 120 | default: 121 | addr->sin.sin_family = AF_INET; 122 | sa = (struct sockaddr*)&addr->sin; 123 | sock_len = sizeof(struct sockaddr_in); 124 | break; 125 | } 126 | 127 | if ((ret = sendto(udp_socket->fd, buf, len, 0, sa, sock_len)) < 0) { 128 | LOGE("Failed to sendto: %s", strerror(errno)); 129 | return -1; 130 | } 131 | 132 | return ret; 133 | } 134 | 135 | int udp_socket_recvfrom(UdpSocket* udp_socket, Address* addr, uint8_t* buf, int len) { 136 | struct sockaddr_in6 sin6; 137 | struct sockaddr_in sin; 138 | struct sockaddr* sa; 139 | socklen_t sock_len; 140 | int ret; 141 | 142 | if (udp_socket->fd < 0) { 143 | LOGE("recvfrom before socket init"); 144 | return -1; 145 | } 146 | 147 | switch (udp_socket->bind_addr.family) { 148 | case AF_INET6: 149 | sin6.sin6_family = AF_INET6; 150 | sa = (struct sockaddr*)&sin6; 151 | sock_len = sizeof(struct sockaddr_in6); 152 | break; 153 | case AF_INET: 154 | default: 155 | sin.sin_family = AF_INET; 156 | sa = (struct sockaddr*)&sin; 157 | sock_len = sizeof(struct sockaddr_in); 158 | break; 159 | } 160 | 161 | if ((ret = recvfrom(udp_socket->fd, buf, len, 0, sa, &sock_len)) < 0) { 162 | LOGE("Failed to recvfrom: %s", strerror(errno)); 163 | return -1; 164 | } 165 | 166 | if (addr) { 167 | switch (udp_socket->bind_addr.family) { 168 | case AF_INET6: 169 | addr->family = AF_INET6; 170 | addr->port = htons(sin6.sin6_port); 171 | memcpy(&addr->sin6, &sin6, sizeof(struct sockaddr_in6)); 172 | break; 173 | case AF_INET: 174 | default: 175 | addr->family = AF_INET; 176 | addr->port = htons(sin.sin_port); 177 | memcpy(&addr->sin, &sin, sizeof(struct sockaddr_in)); 178 | break; 179 | } 180 | } 181 | 182 | return ret; 183 | } 184 | 185 | int tcp_socket_open(TcpSocket* tcp_socket, int family) { 186 | tcp_socket->bind_addr.family = family; 187 | switch (family) { 188 | case AF_INET6: 189 | tcp_socket->fd = socket(AF_INET6, SOCK_STREAM, 0); 190 | break; 191 | case AF_INET: 192 | default: 193 | tcp_socket->fd = socket(AF_INET, SOCK_STREAM, 0); 194 | break; 195 | } 196 | 197 | if (tcp_socket->fd < 0) { 198 | LOGE("Failed to create socket"); 199 | return -1; 200 | } 201 | return 0; 202 | } 203 | 204 | int tcp_socket_connect(TcpSocket* tcp_socket, Address* addr) { 205 | char addr_string[ADDRSTRLEN]; 206 | int ret; 207 | struct sockaddr* sa; 208 | socklen_t sock_len; 209 | 210 | if (tcp_socket->fd < 0) { 211 | LOGE("Connect before socket init"); 212 | return -1; 213 | } 214 | 215 | switch (addr->family) { 216 | case AF_INET6: 217 | addr->sin6.sin6_family = AF_INET6; 218 | sa = (struct sockaddr*)&addr->sin6; 219 | sock_len = sizeof(struct sockaddr_in6); 220 | break; 221 | case AF_INET: 222 | default: 223 | addr->sin.sin_family = AF_INET; 224 | sa = (struct sockaddr*)&addr->sin; 225 | sock_len = sizeof(struct sockaddr_in); 226 | break; 227 | } 228 | 229 | addr_to_string(addr, addr_string, sizeof(addr_string)); 230 | LOGI("Connecting to server: %s:%d", addr_string, addr->port); 231 | if ((ret = connect(tcp_socket->fd, sa, sock_len)) < 0) { 232 | LOGE("Failed to connect to server"); 233 | return -1; 234 | } 235 | 236 | LOGI("Server is connected"); 237 | return 0; 238 | } 239 | 240 | void tcp_socket_close(TcpSocket* tcp_socket) { 241 | if (tcp_socket->fd > 0) { 242 | close(tcp_socket->fd); 243 | } 244 | } 245 | 246 | int tcp_socket_send(TcpSocket* tcp_socket, const uint8_t* buf, int len) { 247 | int ret; 248 | 249 | if (tcp_socket->fd < 0) { 250 | LOGE("sendto before socket init"); 251 | return -1; 252 | } 253 | 254 | ret = send(tcp_socket->fd, buf, len, 0); 255 | if (ret < 0) { 256 | LOGE("Failed to send: %s", strerror(errno)); 257 | return -1; 258 | } 259 | return ret; 260 | } 261 | 262 | int tcp_socket_recv(TcpSocket* tcp_socket, uint8_t* buf, int len) { 263 | int ret; 264 | 265 | if (tcp_socket->fd < 0) { 266 | LOGE("recvfrom before socket init"); 267 | return -1; 268 | } 269 | 270 | ret = recv(tcp_socket->fd, buf, len, 0); 271 | if (ret < 0) { 272 | LOGE("Failed to recv: %s", strerror(errno)); 273 | return -1; 274 | } 275 | return ret; 276 | } 277 | -------------------------------------------------------------------------------- /src/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCKET_H_ 2 | #define SOCKET_H_ 3 | 4 | #include "address.h" 5 | 6 | typedef struct UdpSocket { 7 | int fd; 8 | Address bind_addr; 9 | } UdpSocket; 10 | 11 | typedef struct TcpSocket { 12 | int fd; 13 | Address bind_addr; 14 | } TcpSocket; 15 | 16 | int udp_socket_open(UdpSocket* udp_socket, int family, int port); 17 | 18 | int udp_socket_bind(UdpSocket* udp_socket, int port); 19 | 20 | void udp_socket_close(UdpSocket* udp_socket); 21 | 22 | int udp_socket_sendto(UdpSocket* udp_socket, Address* bind_addr, const uint8_t* buf, int len); 23 | 24 | int udp_socket_recvfrom(UdpSocket* udp_sock, Address* bind_addr, uint8_t* buf, int len); 25 | 26 | int udp_socket_add_multicast_group(UdpSocket* udp_socket, Address* mcast_addr); 27 | 28 | int tcp_socket_open(TcpSocket* tcp_socket, int family); 29 | 30 | int tcp_socket_connect(TcpSocket* tcp_socket, Address* addr); 31 | 32 | void tcp_socket_close(TcpSocket* tcp_socket); 33 | 34 | int tcp_socket_send(TcpSocket* tcp_socket, const uint8_t* buf, int len); 35 | 36 | int tcp_socket_recv(TcpSocket* tcp_socket, uint8_t* buf, int len); 37 | 38 | #endif // SOCKET_H_ 39 | -------------------------------------------------------------------------------- /src/ssl_transport.c: -------------------------------------------------------------------------------- 1 | #ifndef DISABLE_PEER_SIGNALING 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mbedtls/ctr_drbg.h" 7 | #include "mbedtls/debug.h" 8 | #include "mbedtls/entropy.h" 9 | #include "mbedtls/ssl.h" 10 | 11 | #include 12 | #include "config.h" 13 | #include "ports.h" 14 | #include "ssl_transport.h" 15 | #include "utils.h" 16 | 17 | static int ssl_transport_mbedtls_recv_timeout(void* ctx, unsigned char* buf, size_t len, uint32_t timeout) { 18 | int ret; 19 | fd_set read_fds; 20 | struct timeval tv; 21 | tv.tv_sec = timeout / 1000; 22 | tv.tv_usec = (timeout % 1000) * 1000; 23 | 24 | FD_ZERO(&read_fds); 25 | FD_SET(((TcpSocket*)ctx)->fd, &read_fds); 26 | 27 | ret = select(((TcpSocket*)ctx)->fd + 1, &read_fds, NULL, NULL, &tv); 28 | if (ret < 0) { 29 | return -1; 30 | } else if (ret == 0) { 31 | // timeout 32 | } else { 33 | if (FD_ISSET(((TcpSocket*)ctx)->fd, &read_fds)) { 34 | ret = tcp_socket_recv((TcpSocket*)ctx, buf, len); 35 | } 36 | } 37 | 38 | return ret; 39 | } 40 | 41 | static int ssl_transport_mbedlts_send(void* ctx, const uint8_t* buf, size_t len) { 42 | return tcp_socket_send((TcpSocket*)ctx, buf, len); 43 | } 44 | 45 | int ssl_transport_connect(NetworkContext_t* net_ctx, 46 | const char* host, 47 | uint16_t port, 48 | const char* cacert) { 49 | const char* pers = "ssl_client"; 50 | int ret; 51 | Address resolved_addr; 52 | 53 | mbedtls_ssl_init(&net_ctx->ssl); 54 | mbedtls_ssl_config_init(&net_ctx->conf); 55 | // mbedtls_x509_crt_init(&net_ctx->cacert); 56 | mbedtls_ctr_drbg_init(&net_ctx->ctr_drbg); 57 | mbedtls_entropy_init(&net_ctx->entropy); 58 | 59 | if ((ret = mbedtls_ctr_drbg_seed(&net_ctx->ctr_drbg, mbedtls_entropy_func, &net_ctx->entropy, 60 | (const unsigned char*)pers, strlen(pers))) != 0) { 61 | return -1; 62 | } 63 | 64 | if ((ret = mbedtls_ssl_config_defaults(&net_ctx->conf, 65 | MBEDTLS_SSL_IS_CLIENT, 66 | MBEDTLS_SSL_TRANSPORT_STREAM, 67 | MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { 68 | LOGE("ssl config error: -0x%x", (unsigned int)-ret); 69 | return -1; 70 | } 71 | 72 | mbedtls_ssl_conf_authmode(&net_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL); 73 | /* 74 | XXX: not sure if this is needed 75 | ret = mbedtls_x509_crt_parse(&net_ctx->cacert, (const unsigned char *) cacert, strlen(cacert) + 1); 76 | if (ret < 0) { 77 | LOGE("ssl parse error: -0x%x", (unsigned int) -ret); 78 | } 79 | mbedtls_ssl_conf_ca_chain(&net_ctx->conf, &net_ctx->cacert, NULL); 80 | */ 81 | 82 | mbedtls_ssl_conf_rng(&net_ctx->conf, mbedtls_ctr_drbg_random, &net_ctx->ctr_drbg); 83 | 84 | if ((ret = mbedtls_ssl_setup(&net_ctx->ssl, &net_ctx->conf)) != 0) { 85 | LOGE("ssl setup error: -0x%x", (unsigned int)-ret); 86 | return -1; 87 | } 88 | 89 | if ((ret = mbedtls_ssl_set_hostname(&net_ctx->ssl, host)) != 0) { 90 | LOGE("ssl set hostname error: -0x%x", (unsigned int)-ret); 91 | return -1; 92 | } 93 | 94 | memset(&resolved_addr, 0, sizeof(resolved_addr)); 95 | tcp_socket_open(&net_ctx->tcp_socket, AF_INET); 96 | ports_resolve_addr(host, &resolved_addr); 97 | addr_set_port(&resolved_addr, port); 98 | if ((ret = tcp_socket_connect(&net_ctx->tcp_socket, &resolved_addr) < 0)) { 99 | return -1; 100 | } 101 | 102 | mbedtls_ssl_conf_read_timeout(&net_ctx->conf, CONFIG_TLS_READ_TIMEOUT); 103 | mbedtls_ssl_set_bio(&net_ctx->ssl, &net_ctx->tcp_socket, 104 | ssl_transport_mbedlts_send, NULL, ssl_transport_mbedtls_recv_timeout); 105 | 106 | LOGI("start to handshake"); 107 | 108 | while ((ret = mbedtls_ssl_handshake(&net_ctx->ssl)) != 0) { 109 | if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { 110 | LOGE("ssl handshake error: -0x%x", (unsigned int)-ret); 111 | } 112 | } 113 | 114 | LOGI("handshake success"); 115 | return 0; 116 | } 117 | 118 | void ssl_transport_disconnect(NetworkContext_t* net_ctx) { 119 | mbedtls_ssl_config_free(&net_ctx->conf); 120 | // mbedtls_x509_crt_free(&net_ctx->cacert); 121 | mbedtls_ctr_drbg_free(&net_ctx->ctr_drbg); 122 | mbedtls_entropy_free(&net_ctx->entropy); 123 | mbedtls_ssl_free(&net_ctx->ssl); 124 | 125 | tcp_socket_close(&net_ctx->tcp_socket); 126 | } 127 | 128 | int32_t ssl_transport_recv(NetworkContext_t* net_ctx, void* buf, size_t len) { 129 | int ret; 130 | memset(buf, 0, len); 131 | ret = mbedtls_ssl_read(&net_ctx->ssl, buf, len); 132 | 133 | return ret; 134 | } 135 | 136 | int32_t ssl_transport_send(NetworkContext_t* net_ctx, const void* buf, size_t len) { 137 | int ret; 138 | 139 | while ((ret = mbedtls_ssl_write(&net_ctx->ssl, buf, len)) <= 0) { 140 | if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { 141 | LOGE("ssl write error: -0x%x", (unsigned int)-ret); 142 | } 143 | } 144 | 145 | return ret; 146 | } 147 | #endif // DISABLE_PEER_SIGNALING 148 | -------------------------------------------------------------------------------- /src/ssl_transport.h: -------------------------------------------------------------------------------- 1 | #ifndef SSL_TRANSPORT_H_ 2 | #define SSL_TRANSPORT_H_ 3 | 4 | #ifndef DISABLE_PEER_SIGNALING 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "socket.h" 12 | #include "transport_interface.h" 13 | 14 | struct NetworkContext { 15 | TcpSocket tcp_socket; 16 | mbedtls_ssl_context ssl; 17 | mbedtls_entropy_context entropy; 18 | mbedtls_ctr_drbg_context ctr_drbg; 19 | mbedtls_ssl_config conf; 20 | mbedtls_x509_crt cacert; 21 | }; 22 | 23 | int ssl_transport_connect(NetworkContext_t* net_ctx, 24 | const char* host, 25 | uint16_t port, 26 | const char* cacert); 27 | 28 | void ssl_transport_disconnect(NetworkContext_t* net_ctx); 29 | 30 | int32_t ssl_transport_recv(NetworkContext_t* net_ctx, void* buf, size_t len); 31 | 32 | int32_t ssl_transport_send(NetworkContext_t* net_ctx, const void* buf, size_t len); 33 | 34 | #endif // DISABLE_PEER_SIGNALING 35 | #endif // SSL_TRANSPORT_H_ 36 | -------------------------------------------------------------------------------- /src/stun.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "stun.h" 8 | #include "utils.h" 9 | 10 | uint32_t CRC32_TABLE[256] = { 11 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 12 | 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 13 | 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 14 | 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 15 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 16 | 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 17 | 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 18 | 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 19 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 20 | 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 21 | 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 22 | 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 23 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 24 | 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 25 | 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 26 | 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 27 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 28 | 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 29 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 30 | 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 31 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 32 | 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 33 | 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 34 | 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 35 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 36 | 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 37 | 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 38 | 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 39 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 40 | 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 41 | 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 42 | 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 43 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 44 | 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 45 | 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 46 | 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 47 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; 48 | 49 | void stun_msg_create(StunMessage* msg, uint16_t type) { 50 | StunHeader* header = (StunHeader*)msg->buf; 51 | header->type = htons(type); 52 | header->length = 0; 53 | header->magic_cookie = htonl(MAGIC_COOKIE); 54 | header->transaction_id[0] = htonl(0x12345678); 55 | header->transaction_id[1] = htonl(0x90abcdef); 56 | header->transaction_id[2] = htonl(0x12345678); 57 | msg->size = sizeof(StunHeader); 58 | } 59 | 60 | int stun_set_mapped_address(char* value, uint8_t* mask, Address* addr) { 61 | int ret, i; 62 | char addr_string[ADDRSTRLEN]; 63 | uint8_t* family = (uint8_t*)(value + 1); 64 | uint16_t* port = (uint16_t*)(value + 2); 65 | uint32_t* val32 = (uint32_t*)(value + 4); 66 | uint16_t* val16 = (uint16_t*)(value + 4); 67 | uint32_t* addr32 = (uint32_t*)(&addr->sin.sin_addr); 68 | uint16_t* addr16 = (uint16_t*)(&addr->sin6.sin6_addr); 69 | 70 | switch (addr->family) { 71 | case AF_INET6: 72 | *family = STUN_FAMILY_IPV6; 73 | for (i = 0; i < 8; i++) { 74 | val16[i] = addr16[i] ^ *(uint16_t*)(mask + 2 * i); 75 | } 76 | ret = 20; 77 | break; 78 | case AF_INET: 79 | default: 80 | *family = STUN_FAMILY_IPV4; 81 | *val32 = *addr32 ^ *(uint32_t*)mask; 82 | ret = 8; 83 | break; 84 | } 85 | 86 | *port = htons(addr->port) ^ *(uint16_t*)mask; 87 | addr_to_string(addr, addr_string, sizeof(addr_string)); 88 | 89 | LOGD("XOR Mapped Address Family: %d", *family); 90 | LOGD("XOR Mapped Address Port: %d (Port XOR: %04x)", addr->port, *port); 91 | LOGD("XOR Mapped Address IP: %s (IP XOR: %08" PRIu32 ")", addr_string, *addr32); 92 | return ret; 93 | } 94 | 95 | void stun_get_mapped_address(char* value, uint8_t* mask, Address* addr) { 96 | int i; 97 | char addr_string[ADDRSTRLEN]; 98 | uint32_t* addr32 = (uint32_t*)&addr->sin.sin_addr; 99 | uint16_t* addr16 = (uint16_t*)&addr->sin6.sin6_addr; 100 | uint8_t family = value[1]; 101 | uint16_t port; 102 | 103 | switch (family) { 104 | case STUN_FAMILY_IPV6: 105 | addr_set_family(addr, AF_INET6); 106 | for (i = 0; i < 8; i++) { 107 | addr16[i] = (*(uint16_t*)(value + 4 + 2 * i) ^ *(uint16_t*)(mask + 2 * i)); 108 | } 109 | break; 110 | case STUN_FAMILY_IPV4: 111 | default: 112 | addr_set_family(addr, AF_INET); 113 | *addr32 = (*(uint32_t*)(value + 4) ^ *(uint32_t*)mask); 114 | break; 115 | } 116 | 117 | port = ntohs(*(uint16_t*)(value + 2) ^ *(uint16_t*)mask); 118 | addr_to_string(addr, addr_string, sizeof(addr_string)); 119 | addr_set_port(addr, port); 120 | 121 | LOGD("XOR Mapped Address Family: %d", family); 122 | LOGD("XOR Mapped Address Port: %d (Port XOR: %04x)", addr->port, port); 123 | LOGD("XOR Mapped Address IP: %s (IP XOR: %08" PRIu32 ")", addr_string, *addr32); 124 | } 125 | 126 | void stun_parse_msg_buf(StunMessage* msg) { 127 | StunHeader* header = (StunHeader*)msg->buf; 128 | 129 | int length = ntohs(header->length) + sizeof(StunHeader); 130 | 131 | int pos = sizeof(StunHeader); 132 | 133 | uint8_t mask[16]; 134 | 135 | msg->stunclass = ntohs(header->type); 136 | if ((msg->stunclass & STUN_CLASS_ERROR) == STUN_CLASS_ERROR) { 137 | msg->stunclass = STUN_CLASS_ERROR; 138 | } else if ((msg->stunclass & STUN_CLASS_INDICATION) == STUN_CLASS_INDICATION) { 139 | msg->stunclass = STUN_CLASS_INDICATION; 140 | } else if ((msg->stunclass & STUN_CLASS_RESPONSE) == STUN_CLASS_RESPONSE) { 141 | msg->stunclass = STUN_CLASS_RESPONSE; 142 | } else if ((msg->stunclass & STUN_CLASS_REQUEST) == STUN_CLASS_REQUEST) { 143 | msg->stunclass = STUN_CLASS_REQUEST; 144 | } 145 | 146 | msg->stunmethod = ntohs(header->type) & 0x0FFF; 147 | if ((msg->stunmethod & STUN_METHOD_ALLOCATE) == STUN_METHOD_ALLOCATE) { 148 | msg->stunmethod = STUN_METHOD_ALLOCATE; 149 | } else if ((msg->stunmethod & STUN_METHOD_BINDING) == STUN_METHOD_BINDING) { 150 | msg->stunmethod = STUN_METHOD_BINDING; 151 | } 152 | 153 | while (pos < length) { 154 | StunAttribute* attr = (StunAttribute*)(msg->buf + pos); 155 | memset(mask, 0, sizeof(mask)); 156 | // LOGD("Attribute Type: 0x%04x", ntohs(attr->type)); 157 | // LOGD("Attribute Length: %d", ntohs(attr->length)); 158 | 159 | switch (ntohs(attr->type)) { 160 | case STUN_ATTR_TYPE_MAPPED_ADDRESS: 161 | stun_get_mapped_address(attr->value, mask, &msg->mapped_addr); 162 | break; 163 | case STUN_ATTR_TYPE_USERNAME: 164 | memset(msg->username, 0, sizeof(msg->username)); 165 | memcpy(msg->username, attr->value, ntohs(attr->length)); 166 | // LOGD("length = %d, Username %s", ntohs(attr->length), msg->username); 167 | break; 168 | case STUN_ATTR_TYPE_MESSAGE_INTEGRITY: 169 | memcpy(msg->message_integrity, attr->value, ntohs(attr->length)); 170 | 171 | char message_integrity_hex[41]; 172 | 173 | for (int i = 0; i < 20; i++) { 174 | sprintf(message_integrity_hex + 2 * i, "%02x", (uint8_t)msg->message_integrity[i]); 175 | } 176 | 177 | break; 178 | case STUN_ATTR_TYPE_LIFETIME: 179 | break; 180 | case STUN_ATTR_TYPE_REALM: 181 | memset(msg->realm, 0, sizeof(msg->realm)); 182 | memcpy(msg->realm, attr->value, ntohs(attr->length)); 183 | LOGD("Realm %s", msg->realm); 184 | break; 185 | case STUN_ATTR_TYPE_NONCE: 186 | memset(msg->nonce, 0, sizeof(msg->nonce)); 187 | memcpy(msg->nonce, attr->value, ntohs(attr->length)); 188 | LOGD("Nonce %s", msg->nonce); 189 | break; 190 | case STUN_ATTR_TYPE_XOR_RELAYED_ADDRESS: 191 | *((uint32_t*)mask) = htonl(MAGIC_COOKIE); 192 | memcpy(mask + 4, header->transaction_id, sizeof(header->transaction_id)); 193 | LOGD("XOR Relayed Address"); 194 | stun_get_mapped_address(attr->value, mask, &msg->relayed_addr); 195 | break; 196 | case STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS: 197 | *((uint32_t*)mask) = htonl(MAGIC_COOKIE); 198 | memcpy(mask + 4, header->transaction_id, sizeof(header->transaction_id)); 199 | stun_get_mapped_address(attr->value, mask, &msg->mapped_addr); 200 | break; 201 | case STUN_ATTR_TYPE_PRIORITY: 202 | break; 203 | case STUN_ATTR_TYPE_USE_CANDIDATE: 204 | // LOGD("Use Candidate"); 205 | break; 206 | case STUN_ATTR_TYPE_FINGERPRINT: 207 | memcpy(&msg->fingerprint, attr->value, ntohs(attr->length)); 208 | // LOGD("Fingerprint: 0x%.4x", msg->fingerprint); 209 | break; 210 | case STUN_ATTR_TYPE_ICE_CONTROLLED: 211 | case STUN_ATTR_TYPE_ICE_CONTROLLING: 212 | case STUN_ATTR_TYPE_NETWORK_COST: 213 | // Do nothing 214 | break; 215 | default: 216 | LOGE("Unknown Attribute Type: 0x%04x", ntohs(attr->type)); 217 | break; 218 | } 219 | 220 | pos += 4 * ((ntohs(attr->length) + 3) / 4) + sizeof(StunAttribute); 221 | } 222 | } 223 | 224 | void stun_calculate_fingerprint(char* buf, size_t len, uint32_t* fingerprint) { 225 | uint32_t c = 0xFFFFFFFF; 226 | int i = 0; 227 | 228 | for (i = 0; i < len; ++i) { 229 | c = CRC32_TABLE[(c ^ buf[i]) & 0xFF] ^ (c >> 8); 230 | } 231 | 232 | *fingerprint = htonl((c ^ 0xFFFFFFFF) ^ STUN_FINGERPRINT_XOR); 233 | } 234 | 235 | int stun_msg_write_attr(StunMessage* msg, StunAttrType type, uint16_t length, char* value) { 236 | StunHeader* header = (StunHeader*)msg->buf; 237 | 238 | StunAttribute* stun_attr = (StunAttribute*)(msg->buf + msg->size); 239 | 240 | stun_attr->type = htons(type); 241 | stun_attr->length = htons(length); 242 | if (value) 243 | memcpy(stun_attr->value, value, length); 244 | 245 | length = 4 * ((length + 3) / 4); 246 | header->length = htons(ntohs(header->length) + sizeof(StunAttribute) + length); 247 | 248 | msg->size += length + sizeof(StunAttribute); 249 | 250 | switch (type) { 251 | case STUN_ATTR_TYPE_REALM: 252 | memcpy(msg->realm, value, length); 253 | break; 254 | case STUN_ATTR_TYPE_NONCE: 255 | memcpy(msg->nonce, value, length); 256 | break; 257 | case STUN_ATTR_TYPE_USERNAME: 258 | memcpy(msg->username, value, length); 259 | break; 260 | default: 261 | break; 262 | } 263 | 264 | return 0; 265 | } 266 | 267 | int stun_msg_finish(StunMessage* msg, StunCredential credential, const char* password, size_t password_len) { 268 | StunHeader* header = (StunHeader*)msg->buf; 269 | StunAttribute* stun_attr; 270 | 271 | uint16_t header_length = ntohs(header->length); 272 | char key[256]; 273 | char hash_key[17]; 274 | memset(key, 0, sizeof(key)); 275 | memset(hash_key, 0, sizeof(hash_key)); 276 | 277 | switch (credential) { 278 | case STUN_CREDENTIAL_LONG_TERM: 279 | snprintf(key, sizeof(key), "%s:%s:%s", msg->username, msg->realm, password); 280 | LOGD("key: %s", key); 281 | utils_get_md5(key, strlen(key), (unsigned char*)hash_key); 282 | password = hash_key; 283 | password_len = 16; 284 | break; 285 | default: 286 | break; 287 | } 288 | 289 | stun_attr = (StunAttribute*)(msg->buf + msg->size); 290 | header->length = htons(header_length + 24); /* HMAC-SHA1 */ 291 | stun_attr->type = htons(STUN_ATTR_TYPE_MESSAGE_INTEGRITY); 292 | stun_attr->length = htons(20); 293 | utils_get_hmac_sha1((char*)msg->buf, msg->size, password, password_len, (unsigned char*)stun_attr->value); 294 | msg->size += sizeof(StunAttribute) + 20; 295 | // FINGERPRINT 296 | 297 | stun_attr = (StunAttribute*)(msg->buf + msg->size); 298 | header->length = htons(header_length + 24 /* HMAC-SHA1 */ + 8 /* FINGERPRINT */); 299 | stun_attr->type = htons(STUN_ATTR_TYPE_FINGERPRINT); 300 | stun_attr->length = htons(4); 301 | stun_calculate_fingerprint((char*)msg->buf, msg->size, (uint32_t*)stun_attr->value); 302 | msg->size += sizeof(StunAttribute) + 4; 303 | return 0; 304 | } 305 | 306 | int stun_probe(uint8_t* buf, size_t size) { 307 | StunHeader* header; 308 | if (size < sizeof(StunHeader)) { 309 | LOGE("STUN message is too short."); 310 | return -1; 311 | } 312 | 313 | header = (StunHeader*)buf; 314 | if (header->magic_cookie != htonl(MAGIC_COOKIE)) { 315 | return -1; 316 | } 317 | 318 | return 0; 319 | } 320 | 321 | #if 0 322 | StunMsgType stun_is_stun_msg(uint8_t *buf, size_t size) { 323 | 324 | if (size < sizeof(StunHeader)) { 325 | //LOGE("STUN message is too short."); 326 | return STUN_MSG_TYPE_INVLID; 327 | } 328 | 329 | StunHeader *header = (StunHeader *)buf; 330 | if (header->magic_cookie != htonl(MAGIC_COOKIE)) { 331 | //LOGE("STUN magic cookie does not match."); 332 | return STUN_MSG_TYPE_INVLID; 333 | } 334 | 335 | if (ntohs(header->type) == STUN_BINDING_REQUEST) { 336 | 337 | return STUN_MSG_TYPE_BINDING_REQUEST; 338 | 339 | } else if (ntohs(header->type) == STUN_BINDING_RESPONSE) { 340 | 341 | return STUN_MSG_TYPE_BINDING_RESPONSE; 342 | 343 | } else if (ntohs(header->type) == STUN_BINDING_ERROR_RESPONSE) { 344 | 345 | return STUN_MSG_TYPE_BINDING_ERROR_RESPONSE; 346 | } else { 347 | 348 | return STUN_MSG_TYPE_INVLID; 349 | } 350 | 351 | return 0; 352 | } 353 | #endif 354 | int stun_msg_is_valid(uint8_t* buf, size_t size, char* password) { 355 | StunMessage msg; 356 | 357 | memcpy(msg.buf, buf, size); 358 | 359 | stun_parse_msg_buf(&msg); 360 | 361 | StunHeader* header = (StunHeader*)msg.buf; 362 | 363 | // FINGERPRINT 364 | uint32_t fingerprint = 0; 365 | size_t length = size - 4 - sizeof(StunAttribute); 366 | stun_calculate_fingerprint((char*)msg.buf, length, &fingerprint); 367 | // LOGD("Fingerprint: 0x%08x", fingerprint); 368 | 369 | if (fingerprint != msg.fingerprint) { 370 | // LOGE("Fingerprint does not match."); 371 | return -1; 372 | } else { 373 | // LOGD("Fingerprint matches."); 374 | } 375 | 376 | // MESSAGE-INTEGRITY 377 | unsigned char message_integrity_hex[41]; 378 | unsigned char message_integrity[20]; 379 | header->length = htons(ntohs(header->length) - 4 - sizeof(StunAttribute)); 380 | length = length - 20 - sizeof(StunAttribute); 381 | utils_get_hmac_sha1((char*)msg.buf, length, password, strlen(password), message_integrity); 382 | for (int i = 0; i < 20; i++) { 383 | sprintf((char*)&message_integrity_hex[2 * i], "%02x", (uint8_t)message_integrity[i]); 384 | } 385 | 386 | // LOGD("message_integrity: 0x%s", message_integrity_hex); 387 | 388 | if (memcmp(message_integrity, msg.message_integrity, 20) != 0) { 389 | // LOGE("Message Integrity does not match."); 390 | return -1; 391 | } else { 392 | // LOGD("Message Integrity matches."); 393 | } 394 | 395 | return 0; 396 | } 397 | -------------------------------------------------------------------------------- /src/stun.h: -------------------------------------------------------------------------------- 1 | #ifndef STUN_H_ 2 | #define STUN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "address.h" 9 | 10 | typedef struct StunHeader StunHeader; 11 | 12 | typedef struct StunAttribute StunAttribute; 13 | 14 | typedef struct StunMessage StunMessage; 15 | 16 | #define STUN_ATTR_BUF_SIZE 256 17 | #define MAGIC_COOKIE 0x2112A442 18 | #define STUN_FINGERPRINT_XOR 0x5354554e 19 | 20 | typedef enum StunClass { 21 | 22 | STUN_CLASS_REQUEST = 0x0000, 23 | STUN_CLASS_INDICATION = 0x0010, 24 | STUN_CLASS_RESPONSE = 0x0100, 25 | STUN_CLASS_ERROR = 0x0110, 26 | 27 | } StunClass; 28 | 29 | typedef enum StunMethod { 30 | 31 | STUN_METHOD_BINDING = 0x0001, 32 | STUN_METHOD_ALLOCATE = 0x0003, 33 | 34 | } StunMethod; 35 | 36 | typedef enum StunAttrType { 37 | 38 | STUN_ATTR_TYPE_MAPPED_ADDRESS = 0x0001, 39 | STUN_ATTR_TYPE_USERNAME = 0x0006, 40 | STUN_ATTR_TYPE_MESSAGE_INTEGRITY = 0x0008, 41 | STUN_ATTR_TYPE_LIFETIME = 0x000d, 42 | STUN_ATTR_TYPE_REALM = 0x0014, 43 | STUN_ATTR_TYPE_NONCE = 0x0015, 44 | STUN_ATTR_TYPE_XOR_RELAYED_ADDRESS = 0x0016, 45 | STUN_ATTR_TYPE_REQUESTED_TRANSPORT = 0x0019, 46 | STUN_ATTR_TYPE_XOR_MAPPED_ADDRESS = 0x0020, 47 | STUN_ATTR_TYPE_PRIORITY = 0x0024, 48 | STUN_ATTR_TYPE_USE_CANDIDATE = 0x0025, 49 | STUN_ATTR_TYPE_FINGERPRINT = 0x8028, 50 | STUN_ATTR_TYPE_ICE_CONTROLLED = 0x8029, 51 | STUN_ATTR_TYPE_ICE_CONTROLLING = 0x802a, 52 | STUN_ATTR_TYPE_SOFTWARE = 0x8022, 53 | // https://datatracker.ietf.org/doc/html/draft-thatcher-ice-network-cost-00 54 | STUN_ATTR_TYPE_NETWORK_COST = 0xc057, 55 | 56 | } StunAttrType; 57 | 58 | typedef enum StunCredential { 59 | 60 | STUN_CREDENTIAL_SHORT_TERM = 0x0001, 61 | STUN_CREDENTIAL_LONG_TERM = 0x0002, 62 | 63 | } StunCredential; 64 | 65 | typedef enum StunFamily { 66 | 67 | STUN_FAMILY_IPV4 = 0x01, 68 | STUN_FAMILY_IPV6 = 0x02, 69 | 70 | } StunFamily; 71 | 72 | struct StunHeader { 73 | uint16_t type; 74 | uint16_t length; 75 | uint32_t magic_cookie; 76 | uint32_t transaction_id[3]; 77 | }; 78 | 79 | struct StunAttribute { 80 | uint16_t type; 81 | uint16_t length; 82 | char value[0]; 83 | }; 84 | 85 | struct StunMessage { 86 | StunClass stunclass; 87 | StunMethod stunmethod; 88 | uint32_t fingerprint; 89 | char message_integrity[20]; 90 | char username[128]; 91 | char realm[64]; 92 | char nonce[64]; 93 | Address mapped_addr; 94 | Address relayed_addr; 95 | uint8_t buf[STUN_ATTR_BUF_SIZE]; 96 | size_t size; 97 | }; 98 | 99 | void stun_msg_create(StunMessage* msg, uint16_t type); 100 | 101 | int stun_set_mapped_address(char* value, uint8_t* mask, Address* addr); 102 | 103 | void stun_get_mapped_address(char* value, uint8_t* mask, Address* addr); 104 | 105 | void stun_msg_parse(StunMessage* msg, uint8_t* buf, size_t len); 106 | 107 | void stun_parse_msg_buf(StunMessage* msg); 108 | 109 | void stun_calculate_fingerprint(char* buf, size_t len, uint32_t* fingerprint); 110 | 111 | int stun_msg_write_attr(StunMessage* msg, StunAttrType type, uint16_t length, char* value); 112 | 113 | int stun_probe(uint8_t* buf, size_t size); 114 | 115 | int stun_msg_is_valid(uint8_t* buf, size_t len, char* password); 116 | 117 | int stun_msg_finish(StunMessage* msg, StunCredential credential, const char* password, size_t password_len); 118 | 119 | #endif // STUN_H_ 120 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "mbedtls/md.h" 8 | 9 | void utils_random_string(char* s, const int len) { 10 | int i; 11 | 12 | static const char alphanum[] = 13 | "0123456789" 14 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 15 | "abcdefghijklmnopqrstuvwxyz"; 16 | 17 | srand(time(NULL)); 18 | 19 | for (i = 0; i < len; ++i) { 20 | s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; 21 | } 22 | 23 | s[len] = '\0'; 24 | } 25 | 26 | void utils_get_hmac_sha1(const char* input, size_t input_len, const char* key, size_t key_len, unsigned char* output) { 27 | mbedtls_md_context_t ctx; 28 | mbedtls_md_type_t md_type = MBEDTLS_MD_SHA1; 29 | mbedtls_md_init(&ctx); 30 | mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1); 31 | mbedtls_md_hmac_starts(&ctx, (const unsigned char*)key, key_len); 32 | mbedtls_md_hmac_update(&ctx, (const unsigned char*)input, input_len); 33 | mbedtls_md_hmac_finish(&ctx, output); 34 | mbedtls_md_free(&ctx); 35 | } 36 | 37 | void utils_get_md5(const char* input, size_t input_len, unsigned char* output) { 38 | mbedtls_md_context_t ctx; 39 | mbedtls_md_type_t md_type = MBEDTLS_MD_MD5; 40 | mbedtls_md_init(&ctx); 41 | mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1); 42 | mbedtls_md_starts(&ctx); 43 | mbedtls_md_update(&ctx, (const unsigned char*)input, input_len); 44 | mbedtls_md_finish(&ctx, output); 45 | mbedtls_md_free(&ctx); 46 | } 47 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H_ 2 | #define UTILS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include "config.h" 8 | 9 | #define LEVEL_ERROR 0x00 10 | #define LEVEL_WARN 0x01 11 | #define LEVEL_INFO 0x02 12 | #define LEVEL_DEBUG 0x03 13 | 14 | #define ERROR_TAG "ERROR" 15 | #define WARN_TAG "WARN" 16 | #define INFO_TAG "INFO" 17 | #define DEBUG_TAG "DEBUG" 18 | 19 | #ifndef LOG_LEVEL 20 | #define LOG_LEVEL LEVEL_INFO 21 | #endif 22 | 23 | #if LOG_REDIRECT 24 | void peer_log(char* level_tag, const char* file_name, int line_number, const char* fmt, ...); 25 | #define LOG_PRINT(level_tag, fmt, ...) \ 26 | peer_log(level_tag, __FILE__, __LINE__, fmt, ##__VA_ARGS__) 27 | #else 28 | #define LOG_PRINT(level_tag, fmt, ...) \ 29 | fprintf(stdout, "%s\t%s\t%d\t" fmt "\n", level_tag, __FILE__, __LINE__, ##__VA_ARGS__) 30 | #endif 31 | 32 | #if LOG_LEVEL >= LEVEL_DEBUG 33 | #define LOGD(fmt, ...) LOG_PRINT(DEBUG_TAG, fmt, ##__VA_ARGS__) 34 | #else 35 | #define LOGD(fmt, ...) 36 | #endif 37 | 38 | #if LOG_LEVEL >= LEVEL_INFO 39 | #define LOGI(fmt, ...) LOG_PRINT(INFO_TAG, fmt, ##__VA_ARGS__) 40 | #else 41 | #define LOGI(fmt, ...) 42 | #endif 43 | 44 | #if LOG_LEVEL >= LEVEL_WARN 45 | #define LOGW(fmt, ...) LOG_PRINT(WARN_TAG, fmt, ##__VA_ARGS__) 46 | #else 47 | #define LOGW(fmt, ...) 48 | #endif 49 | 50 | #if LOG_LEVEL >= LEVEL_ERROR 51 | #define LOGE(fmt, ...) LOG_PRINT(ERROR_TAG, fmt, ##__VA_ARGS__) 52 | #else 53 | #define LOGE(fmt, ...) 54 | #endif 55 | 56 | #define ALIGN32(num) ((num + 3) & ~3) 57 | 58 | void utils_random_string(char* s, const int len); 59 | 60 | void utils_get_hmac_sha1(const char* input, size_t input_len, const char* key, size_t key_len, unsigned char* output); 61 | 62 | void utils_get_md5(const char* input, size_t input_len, unsigned char* output); 63 | 64 | #endif // UTILS_H_ 65 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(tests) 2 | 3 | file(GLOB SRCS "*.c") 4 | 5 | include_directories(${PROJECT_SOURCE_DIR}/../src) 6 | 7 | foreach(sourcefile ${SRCS}) 8 | string(REPLACE ".c" "" appname ${sourcefile}) 9 | string(REPLACE "${PROJECT_SOURCE_DIR}/" "" appname ${appname}) 10 | add_executable(${appname} ${sourcefile}) 11 | target_link_libraries(${appname} peer pthread) 12 | endforeach(sourcefile ${TEST_SRCS}) 13 | 14 | target_link_libraries(test_peer_connection cjson) 15 | 16 | -------------------------------------------------------------------------------- /tests/test_agent.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "agent.h" 5 | #include "ports.h" 6 | 7 | void test_gather_host(Agent* agent) { 8 | agent_gather_candidate(agent, NULL, NULL, NULL); 9 | } 10 | 11 | void test_gather_turn(Agent* agent, char* turnserver, char* username, char* credential) { 12 | agent_gather_candidate(agent, turnserver, username, credential); 13 | } 14 | 15 | void test_gather_stun(Agent* agent, char* stunserver) { 16 | agent_gather_candidate(agent, stunserver, NULL, NULL); 17 | } 18 | 19 | int main(int argc, char* argv[]) { 20 | Agent agent; 21 | 22 | char stunserver[] = "stun:stun.l.google.com:19302"; 23 | char turnserver[] = ""; 24 | char username[] = ""; 25 | char credential[] = ""; 26 | char description[1024]; 27 | memset(&description, 0, sizeof(description)); 28 | 29 | agent_create(&agent); 30 | 31 | test_gather_host(&agent); 32 | test_gather_stun(&agent, stunserver); 33 | test_gather_turn(&agent, turnserver, username, credential); 34 | agent_get_local_description(&agent, description, sizeof(description)); 35 | 36 | printf("sdp:\n%s\n", description); 37 | 38 | agent_destroy(&agent); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /tests/test_dtls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dtls_srtp.h" 7 | 8 | void test_handshake(int argc, char* argv[]) { 9 | #if 0 10 | DtlsSrtp dtls_srtp; 11 | UdpSocket udp_socket; 12 | Address local_addr; 13 | Address remote_addr; 14 | 15 | if (argc < 2) { 16 | 17 | printf("Usage: %s client/server\n", argv[0]); 18 | return; 19 | } 20 | 21 | local_addr.ipv4[0] = 192; 22 | local_addr.ipv4[1] = 168; 23 | local_addr.ipv4[2] = 1; 24 | local_addr.ipv4[3] = 110; 25 | 26 | remote_addr.ipv4[0] = 192; 27 | remote_addr.ipv4[1] = 168; 28 | remote_addr.ipv4[2] = 1; 29 | remote_addr.ipv4[3] = 110; 30 | 31 | if (strstr(argv[1], "client")) { 32 | 33 | local_addr.port = 1234; 34 | remote_addr.port = 5677; // 5678 for MNDP 35 | dtls_srtp_init(&dtls_srtp, DTLS_SRTP_ROLE_CLIENT, &udp_socket); 36 | 37 | } else { 38 | 39 | local_addr.port = 5677; // 5678 for MNDP 40 | remote_addr.port = 1234; 41 | dtls_srtp_init(&dtls_srtp, DTLS_SRTP_ROLE_SERVER, &udp_socket); 42 | } 43 | 44 | udp_socket_open(&udp_socket); 45 | 46 | udp_socket_bind(&udp_socket, &local_addr); 47 | 48 | dtls_srtp_handshake(&dtls_srtp, &remote_addr); 49 | 50 | char buf[64]; 51 | 52 | memset(buf, 0, sizeof(buf)); 53 | 54 | if (strstr(argv[1], "client")) { 55 | 56 | snprintf(buf, sizeof(buf), "hello from client"); 57 | 58 | printf("client sending: %s\n", buf); 59 | 60 | usleep(100 * 1000); 61 | 62 | dtls_srtp_write(&dtls_srtp, buf, sizeof(buf)); 63 | 64 | dtls_srtp_read(&dtls_srtp, buf, sizeof(buf)); 65 | 66 | printf("client received: %s\n", buf); 67 | 68 | } else { 69 | 70 | dtls_srtp_read(&dtls_srtp, buf, sizeof(buf)); 71 | 72 | printf("server received: %s\n", buf); 73 | 74 | snprintf(buf, sizeof(buf), "hello from server"); 75 | 76 | printf("server sending: %s\n", buf); 77 | 78 | usleep(100 * 1000); 79 | 80 | dtls_srtp_write(&dtls_srtp, buf, sizeof(buf)); 81 | 82 | } 83 | 84 | dtls_srtp_deinit(&dtls_srtp); 85 | #endif 86 | } 87 | 88 | void test_reset() { 89 | DtlsSrtp dtls_srtp; 90 | dtls_srtp_init(&dtls_srtp, DTLS_SRTP_ROLE_CLIENT, NULL); 91 | dtls_srtp_deinit(&dtls_srtp); 92 | } 93 | 94 | int main(int argc, char* argv[]) { 95 | test_reset(); 96 | test_handshake(argc, argv); 97 | 98 | return 0; 99 | } 100 | -------------------------------------------------------------------------------- /tests/test_peer_connection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "peer.h" 7 | 8 | #define MAX_CONNECTION_ATTEMPTS 25 9 | #define OFFER_DATACHANNEL_MESSAGE "Hello World" 10 | #define ANSWER_DATACHANNEL_MESSAGE "Foobar" 11 | #define DATACHANNEL_NAME "libpeer-datachannel" 12 | 13 | int test_complete = 0; 14 | 15 | typedef struct { 16 | PeerConnection *offer_peer_connection, *answer_peer_connection; 17 | int onmessage_offer_called, onmessage_answer_called, test_complete; 18 | } TestUserData; 19 | 20 | static void onconnectionstatechange_offerer_peer_connection(PeerConnectionState state, void* user_data) { 21 | printf("offer state is changed: %s\n", peer_connection_state_to_string(state)); 22 | } 23 | 24 | static void onconnectionstatechange_answerer_peer_connection(PeerConnectionState state, void* user_data) { 25 | printf("answerer state is changed: %s\n", peer_connection_state_to_string(state)); 26 | } 27 | 28 | static void onicecandidate_offerer_peer_connection(char* description, void* user_data) { 29 | TestUserData* test_user_data = (TestUserData*)user_data; 30 | peer_connection_set_remote_description(test_user_data->answer_peer_connection, description); 31 | } 32 | 33 | static void onicecandidate_answerer_peer_connection(char* description, void* user_data) { 34 | TestUserData* test_user_data = (TestUserData*)user_data; 35 | peer_connection_set_remote_description(test_user_data->offer_peer_connection, description); 36 | } 37 | 38 | static void ondatachannel_onmessage_offerer_peer_connection(char* msg, size_t len, void* userdata, uint16_t sid) { 39 | TestUserData* test_user_data = (TestUserData*)userdata; 40 | 41 | if (strcmp(msg, ANSWER_DATACHANNEL_MESSAGE) == 0) { 42 | test_user_data->onmessage_offer_called = 1; 43 | } 44 | } 45 | 46 | static void ondatachannel_onmessage_answerer_peer_connection(char* msg, size_t len, void* userdata, uint16_t sid) { 47 | TestUserData* test_user_data = (TestUserData*)userdata; 48 | 49 | if (strcmp(msg, OFFER_DATACHANNEL_MESSAGE) == 0) { 50 | test_user_data->onmessage_answer_called = 1; 51 | } 52 | } 53 | 54 | static void* peer_connection_task(void* user_data) { 55 | PeerConnection* peer_connection = (PeerConnection*)user_data; 56 | 57 | while (!test_complete) { 58 | peer_connection_loop(peer_connection); 59 | usleep(1000); 60 | } 61 | 62 | pthread_exit(NULL); 63 | return NULL; 64 | } 65 | 66 | int main(int argc, char* argv[]) { 67 | pthread_t offer_thread, answer_thread; 68 | 69 | TestUserData test_user_data = { 70 | .offer_peer_connection = NULL, 71 | .answer_peer_connection = NULL, 72 | }; 73 | 74 | PeerConfiguration config = { 75 | .ice_servers = { 76 | {.urls = "stun:stun.l.google.com:19302"}, 77 | }, 78 | .datachannel = DATA_CHANNEL_STRING, 79 | .video_codec = CODEC_H264, 80 | .audio_codec = CODEC_OPUS, 81 | .user_data = &test_user_data, 82 | }; 83 | 84 | peer_init(); 85 | 86 | test_user_data.offer_peer_connection = peer_connection_create(&config); 87 | test_user_data.answer_peer_connection = peer_connection_create(&config); 88 | 89 | peer_connection_oniceconnectionstatechange(test_user_data.offer_peer_connection, onconnectionstatechange_offerer_peer_connection); 90 | peer_connection_oniceconnectionstatechange(test_user_data.answer_peer_connection, onconnectionstatechange_answerer_peer_connection); 91 | 92 | peer_connection_onicecandidate(test_user_data.offer_peer_connection, onicecandidate_offerer_peer_connection); 93 | peer_connection_onicecandidate(test_user_data.answer_peer_connection, onicecandidate_answerer_peer_connection); 94 | 95 | peer_connection_ondatachannel(test_user_data.offer_peer_connection, ondatachannel_onmessage_offerer_peer_connection, NULL, NULL); 96 | peer_connection_ondatachannel(test_user_data.answer_peer_connection, ondatachannel_onmessage_answerer_peer_connection, NULL, NULL); 97 | 98 | peer_connection_create_offer(test_user_data.offer_peer_connection); 99 | 100 | pthread_create(&offer_thread, NULL, peer_connection_task, test_user_data.offer_peer_connection); 101 | pthread_create(&answer_thread, NULL, peer_connection_task, test_user_data.answer_peer_connection); 102 | 103 | int attempts = 0, datachannel_created = 0; 104 | while (attempts < MAX_CONNECTION_ATTEMPTS) { 105 | if (!datachannel_created && peer_connection_get_state(test_user_data.offer_peer_connection) == PEER_CONNECTION_COMPLETED) { 106 | if (peer_connection_create_datachannel(test_user_data.offer_peer_connection, DATA_CHANNEL_RELIABLE, 0, 0, DATACHANNEL_NAME, "bar") == 18) { 107 | datachannel_created = 1; 108 | } 109 | } 110 | 111 | if (peer_connection_get_state(test_user_data.offer_peer_connection) == PEER_CONNECTION_COMPLETED && 112 | peer_connection_get_state(test_user_data.answer_peer_connection) == PEER_CONNECTION_COMPLETED && 113 | test_user_data.onmessage_offer_called == 1 && 114 | test_user_data.onmessage_answer_called == 1) { 115 | break; 116 | } 117 | 118 | peer_connection_datachannel_send(test_user_data.offer_peer_connection, OFFER_DATACHANNEL_MESSAGE, sizeof(OFFER_DATACHANNEL_MESSAGE)); 119 | peer_connection_datachannel_send(test_user_data.answer_peer_connection, ANSWER_DATACHANNEL_MESSAGE, sizeof(ANSWER_DATACHANNEL_MESSAGE)); 120 | 121 | attempts++; 122 | usleep(250000); 123 | } 124 | 125 | if (strcmp(DATACHANNEL_NAME, peer_connection_lookup_sid_label(test_user_data.answer_peer_connection, 0)) != 0) { 126 | return 1; 127 | } 128 | 129 | test_complete = 1; 130 | peer_connection_destroy(test_user_data.offer_peer_connection); 131 | peer_connection_destroy(test_user_data.answer_peer_connection); 132 | 133 | peer_deinit(); 134 | return attempts == MAX_CONNECTION_ATTEMPTS ? 1 : 0; 135 | } 136 | -------------------------------------------------------------------------------- /tests/test_rb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "buffer.h" 5 | 6 | #define MTU 800 7 | int g_data_length[8] = {100, 200, 300, 400, 500, 600, 700, 800}; 8 | int g_testing = 1; 9 | 10 | int check_data(uint8_t* data, int length) { 11 | int sum = 0; 12 | int value = length * (length / 100 + '0'); 13 | 14 | for (int i = 0; i < length; i++) { 15 | sum += data[i]; 16 | } 17 | 18 | printf("sum: %d, value: %d\n", sum, value); 19 | return sum == value; 20 | } 21 | 22 | void* test_thread(void* arg) { 23 | Buffer* rb = (Buffer*)arg; 24 | 25 | int size = 0; 26 | 27 | uint8_t* data = NULL; 28 | 29 | while (g_testing) { 30 | data = buffer_peak_head(rb, &size); 31 | 32 | if (data) { 33 | if (check_data(data, size) == 0) { 34 | printf("data error\n"); 35 | exit(1); 36 | } 37 | 38 | buffer_pop_head(rb); 39 | 40 | } else { 41 | usleep(1000); 42 | } 43 | } 44 | 45 | return NULL; 46 | } 47 | 48 | int main(int argc, char* argv[]) { 49 | Buffer* rb = buffer_new(2000); 50 | pthread_t thread; 51 | 52 | pthread_create(&thread, NULL, test_thread, rb); 53 | 54 | uint8_t data[MTU]; 55 | 56 | for (int i = 0; i < 8; i++) { 57 | memset(data, i + '1', MTU); 58 | buffer_push_tail(rb, data, g_data_length[i]); 59 | usleep(1000); 60 | } 61 | 62 | usleep(100 * 1000); 63 | 64 | g_testing = 0; 65 | 66 | pthread_join(thread, NULL); 67 | 68 | printf("test success\n"); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /tests/test_sdp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "agent.h" 6 | 7 | void on_agent_state_changed(AgentState state, void* user_data) { 8 | printf("Agent state changed: %d\n", state); 9 | } 10 | 11 | int main(int argc, char* argv[]) { 12 | #if 0 13 | Agent agent; 14 | 15 | char remote_description[AGENT_MAX_DESCRIPTION]; 16 | char remote_description_base64[AGENT_MAX_DESCRIPTION]; 17 | char local_description[AGENT_MAX_DESCRIPTION]; 18 | char local_description_base64[AGENT_MAX_DESCRIPTION]; 19 | 20 | if (argc < 2) { 21 | 22 | printf("Usage: %s peer_id\n", argv[0]); 23 | return 0; 24 | } 25 | 26 | memset(remote_description, 0, sizeof(remote_description)); 27 | memset(remote_description_base64, 0, sizeof(remote_description_base64)); 28 | 29 | memset(local_description, 0, sizeof(local_description)); 30 | memset(local_description_base64, 0, sizeof(local_description_base64)); 31 | 32 | memset(&agent, 0, sizeof(agent)); 33 | 34 | agent_gather_candidates(&agent); 35 | 36 | snprintf(local_description, sizeof(local_description), "m=text 50327 ICE/SDP\nc=IN IP4 0.0.0.0\n"); 37 | 38 | agent_get_local_description(&agent, local_description + strlen(local_description), sizeof(local_description)); 39 | 40 | base64_encode(local_description, strlen(local_description), local_description_base64, sizeof(local_description_base64)); 41 | 42 | printf("Local description: \n%s\n", local_description); 43 | 44 | printf("Local description base64: \n%s\n", local_description_base64); 45 | 46 | printf("Enter remote description base64: \n"); 47 | 48 | scanf("%s", remote_description_base64); 49 | 50 | base64_decode(remote_description_base64, strlen(remote_description_base64), remote_description, sizeof(remote_description)); 51 | 52 | printf("Remote description: \n%s", remote_description); 53 | 54 | agent.mode = AGENT_MODE_CONTROLLING; 55 | //agent.mode = AGENT_MODE_CONTROLLED; 56 | agent_set_remote_description(&agent, remote_description); 57 | #endif 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /tests/test_stun.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "stun.h" 6 | 7 | int main(int argc, char* argv[]) { 8 | } 9 | --------------------------------------------------------------------------------