diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..63f29c0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignConsecutiveMacros: true +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +BinPackArguments: false +ColumnLimit: 0 +ContinuationIndentWidth: 2 +FixNamespaceComments: false +IndentAccessModifiers: true +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: 2 +NamespaceIndentation: All +PointerAlignment: Left +ReferenceAlignment: Left +TabWidth: 2 +UseTab: Never diff --git a/.clang-format copy b/.clang-format copy new file mode 100644 index 0000000..63f29c0 --- /dev/null +++ b/.clang-format copy @@ -0,0 +1,22 @@ +Language: Cpp +BasedOnStyle: LLVM + +AccessModifierOffset: -2 +AlignConsecutiveMacros: true +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: Inline +BinPackArguments: false +ColumnLimit: 0 +ContinuationIndentWidth: 2 +FixNamespaceComments: false +IndentAccessModifiers: true +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: 2 +NamespaceIndentation: All +PointerAlignment: Left +ReferenceAlignment: Left +TabWidth: 2 +UseTab: Never diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..935eb4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Please make sure to go through the recommendations before opening a bug report:** + +[https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations](https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations) + +**Description** + +A clear and concise description of what the bug is. + +**Board** + +esp32dev, esp32s3, etc + +**Ethernet adapter used ?** + +If yes, please specify which one + +**Stack trace** + +Please provide the stack trace here taken with `monitor_filters = esp32_exception_decoder`. +**Any issue opened with a non readable stack trace will be ignored because not helpful at all.** + +As an alternative, you can use [https://maximeborges.github.io/esp-stacktrace-decoder/](https://maximeborges.github.io/esp-stacktrace-decoder/). + +**Additional notes** + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..634179c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,10 @@ +--- +name: Question +about: Describe your question +title: "[Q]" +labels: question +assignees: '' + +--- + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..dfd0e30 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/.github/scripts/install-arduino-core-esp32.sh b/.github/scripts/install-arduino-core-esp32.sh deleted file mode 100755 index cf1026d..0000000 --- a/.github/scripts/install-arduino-core-esp32.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -export ARDUINO_ESP32_PATH="$ARDUINO_USR_PATH/hardware/espressif/esp32" -if [ ! -d "$ARDUINO_ESP32_PATH" ]; then - echo "Installing ESP32 Arduino Core ..." - script_init_path="$PWD" - mkdir -p "$ARDUINO_USR_PATH/hardware/espressif" - cd "$ARDUINO_USR_PATH/hardware/espressif" - - echo "Installing Python Serial ..." - pip install pyserial > /dev/null - - if [ "$OS_IS_WINDOWS" == "1" ]; then - echo "Installing Python Requests ..." - pip install requests > /dev/null - fi - - if [ "$GITHUB_REPOSITORY" == "espressif/arduino-esp32" ]; then - echo "Linking Core..." - ln -s $GITHUB_WORKSPACE esp32 - else - echo "Cloning Core Repository..." - git clone https://github.com/espressif/arduino-esp32.git esp32 > /dev/null 2>&1 - fi - - echo "Updating Submodules ..." - cd esp32 - git submodule update --init --recursive > /dev/null 2>&1 - - echo "Installing Platform Tools ..." - cd tools && python get.py - cd $script_init_path - - echo "ESP32 Arduino has been installed in '$ARDUINO_ESP32_PATH'" - echo "" -fi diff --git a/.github/scripts/install-arduino-ide.sh b/.github/scripts/install-arduino-ide.sh deleted file mode 100755 index 7e268b1..0000000 --- a/.github/scripts/install-arduino-ide.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/bin/bash - -#OSTYPE: 'linux-gnu', ARCH: 'x86_64' => linux64 -#OSTYPE: 'msys', ARCH: 'x86_64' => win32 -#OSTYPE: 'darwin18', ARCH: 'i386' => macos - -OSBITS=`arch` -if [[ "$OSTYPE" == "linux"* ]]; then - export OS_IS_LINUX="1" - ARCHIVE_FORMAT="tar.xz" - if [[ "$OSBITS" == "i686" ]]; then - OS_NAME="linux32" - elif [[ "$OSBITS" == "x86_64" ]]; then - OS_NAME="linux64" - elif [[ "$OSBITS" == "armv7l" || "$OSBITS" == "aarch64" ]]; then - OS_NAME="linuxarm" - else - OS_NAME="$OSTYPE-$OSBITS" - echo "Unknown OS '$OS_NAME'" - exit 1 - fi -elif [[ "$OSTYPE" == "darwin"* ]]; then - export OS_IS_MACOS="1" - ARCHIVE_FORMAT="zip" - OS_NAME="macosx" -elif [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then - export OS_IS_WINDOWS="1" - ARCHIVE_FORMAT="zip" - OS_NAME="windows" -else - OS_NAME="$OSTYPE-$OSBITS" - echo "Unknown OS '$OS_NAME'" - exit 1 -fi -export OS_NAME - -ARDUINO_BUILD_DIR="$HOME/.arduino/build.tmp" -ARDUINO_CACHE_DIR="$HOME/.arduino/cache.tmp" - -if [ "$OS_IS_MACOS" == "1" ]; then - export ARDUINO_IDE_PATH="/Applications/Arduino.app/Contents/Java" - export ARDUINO_USR_PATH="$HOME/Documents/Arduino" -elif [ "$OS_IS_WINDOWS" == "1" ]; then - export ARDUINO_IDE_PATH="$HOME/arduino_ide" - export ARDUINO_USR_PATH="$HOME/Documents/Arduino" -else - export ARDUINO_IDE_PATH="$HOME/arduino_ide" - export ARDUINO_USR_PATH="$HOME/Arduino" -fi - -if [ ! -d "$ARDUINO_IDE_PATH" ]; then - echo "Installing Arduino IDE on $OS_NAME ..." - echo "Downloading 'arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT' to 'arduino.$ARCHIVE_FORMAT' ..." - if [ "$OS_IS_LINUX" == "1" ]; then - wget -O "arduino.$ARCHIVE_FORMAT" "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 - echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." - tar xf "arduino.$ARCHIVE_FORMAT" > /dev/null - mv arduino-nightly "$ARDUINO_IDE_PATH" - else - curl -o "arduino.$ARCHIVE_FORMAT" -L "https://www.arduino.cc/download.php?f=/arduino-nightly-$OS_NAME.$ARCHIVE_FORMAT" > /dev/null 2>&1 - echo "Extracting 'arduino.$ARCHIVE_FORMAT' ..." - unzip "arduino.$ARCHIVE_FORMAT" > /dev/null - if [ "$OS_IS_MACOS" == "1" ]; then - mv "Arduino.app" "/Applications/Arduino.app" - else - mv arduino-nightly "$ARDUINO_IDE_PATH" - fi - fi - rm -rf "arduino.$ARCHIVE_FORMAT" - - mkdir -p "$ARDUINO_USR_PATH/libraries" - mkdir -p "$ARDUINO_USR_PATH/hardware" - - echo "Arduino IDE Installed in '$ARDUINO_IDE_PATH'" - echo "" -fi - -function build_sketch(){ # build_sketch [extra-options] - if [ "$#" -lt 2 ]; then - echo "ERROR: Illegal number of parameters" - echo "USAGE: build_sketch [extra-options]" - return 1 - fi - - local fqbn="$1" - local sketch="$2" - local xtra_opts="$3" - local win_opts="" - if [ "$OS_IS_WINDOWS" == "1" ]; then - local ctags_version=`ls "$ARDUINO_IDE_PATH/tools-builder/ctags/"` - local preprocessor_version=`ls "$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/"` - win_opts="-prefs=runtime.tools.ctags.path=$ARDUINO_IDE_PATH/tools-builder/ctags/$ctags_version -prefs=runtime.tools.arduino-preprocessor.path=$ARDUINO_IDE_PATH/tools-builder/arduino-preprocessor/$preprocessor_version" - fi - - echo "" - echo "Compiling '"$(basename "$sketch")"' ..." - mkdir -p "$ARDUINO_BUILD_DIR" - mkdir -p "$ARDUINO_CACHE_DIR" - $ARDUINO_IDE_PATH/arduino-builder -compile -logger=human -core-api-version=10810 \ - -fqbn=$fqbn \ - -warnings="all" \ - -tools "$ARDUINO_IDE_PATH/tools-builder" \ - -tools "$ARDUINO_IDE_PATH/tools" \ - -built-in-libraries "$ARDUINO_IDE_PATH/libraries" \ - -hardware "$ARDUINO_IDE_PATH/hardware" \ - -hardware "$ARDUINO_USR_PATH/hardware" \ - -libraries "$ARDUINO_USR_PATH/libraries" \ - -build-cache "$ARDUINO_CACHE_DIR" \ - -build-path "$ARDUINO_BUILD_DIR" \ - $win_opts $xtra_opts "$sketch" -} - -function count_sketches() # count_sketches -{ - local examples="$1" - rm -rf sketches.txt - if [ ! -d "$examples" ]; then - touch sketches.txt - return 0 - fi - local sketches=$(find $examples -name *.ino) - local sketchnum=0 - for sketch in $sketches; do - local sketchdir=$(dirname $sketch) - local sketchdirname=$(basename $sketchdir) - local sketchname=$(basename $sketch) - if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then - continue - fi; - if [[ -f "$sketchdir/.test.skip" ]]; then - continue - fi - echo $sketch >> sketches.txt - sketchnum=$(($sketchnum + 1)) - done - return $sketchnum -} - -function build_sketches() # build_sketches [extra-options] -{ - local fqbn=$1 - local examples=$2 - local chunk_idex=$3 - local chunks_num=$4 - local xtra_opts=$5 - - if [ "$#" -lt 2 ]; then - echo "ERROR: Illegal number of parameters" - echo "USAGE: build_sketches [ ] [extra-options]" - return 1 - fi - - if [ "$#" -lt 4 ]; then - chunk_idex="0" - chunks_num="1" - xtra_opts=$3 - fi - - if [ "$chunks_num" -le 0 ]; then - echo "ERROR: Chunks count must be positive number" - return 1 - fi - if [ "$chunk_idex" -ge "$chunks_num" ]; then - echo "ERROR: Chunk index must be less than chunks count" - return 1 - fi - - set +e - count_sketches "$examples" - local sketchcount=$? - set -e - local sketches=$(cat sketches.txt) - rm -rf sketches.txt - - local chunk_size=$(( $sketchcount / $chunks_num )) - local all_chunks=$(( $chunks_num * $chunk_size )) - if [ "$all_chunks" -lt "$sketchcount" ]; then - chunk_size=$(( $chunk_size + 1 )) - fi - - local start_index=$(( $chunk_idex * $chunk_size )) - if [ "$sketchcount" -le "$start_index" ]; then - echo "Skipping job" - return 0 - fi - - local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) - if [ "$end_index" -gt "$sketchcount" ]; then - end_index=$sketchcount - fi - - local start_num=$(( $start_index + 1 )) - echo "Found $sketchcount Sketches"; - echo "Chunk Count : $chunks_num" - echo "Chunk Size : $chunk_size" - echo "Start Sketch: $start_num" - echo "End Sketch : $end_index" - - local sketchnum=0 - for sketch in $sketches; do - local sketchdir=$(dirname $sketch) - local sketchdirname=$(basename $sketchdir) - local sketchname=$(basename $sketch) - if [ "${sketchdirname}.ino" != "$sketchname" ] \ - || [ -f "$sketchdir/.test.skip" ]; then - continue - fi - sketchnum=$(($sketchnum + 1)) - if [ "$sketchnum" -le "$start_index" ] \ - || [ "$sketchnum" -gt "$end_index" ]; then - continue - fi - build_sketch "$fqbn" "$sketch" "$xtra_opts" - local result=$? - if [ $result -ne 0 ]; then - return $result - fi - done - return 0 -} diff --git a/.github/scripts/install-platformio.sh b/.github/scripts/install-platformio.sh deleted file mode 100644 index 61c94fe..0000000 --- a/.github/scripts/install-platformio.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/bash - -echo "Installing Python Wheel ..." -pip install wheel > /dev/null 2>&1 - -echo "Installing PlatformIO ..." -pip install -U platformio > /dev/null 2>&1 - -echo "PlatformIO has been installed" -echo "" - - -function build_pio_sketch(){ # build_pio_sketch - if [ "$#" -lt 2 ]; then - echo "ERROR: Illegal number of parameters" - echo "USAGE: build_pio_sketch " - return 1 - fi - - local board="$1" - local sketch="$2" - local sketch_dir=$(dirname "$sketch") - echo "" - echo "Compiling '"$(basename "$sketch")"' ..." - python -m platformio ci -l '.' --board "$board" "$sketch_dir" --project-option="board_build.partitions = huge_app.csv" -} - -function count_sketches() # count_sketches -{ - local examples="$1" - rm -rf sketches.txt - if [ ! -d "$examples" ]; then - touch sketches.txt - return 0 - fi - local sketches=$(find $examples -name *.ino) - local sketchnum=0 - for sketch in $sketches; do - local sketchdir=$(dirname $sketch) - local sketchdirname=$(basename $sketchdir) - local sketchname=$(basename $sketch) - if [[ "${sketchdirname}.ino" != "$sketchname" ]]; then - continue - fi; - if [[ -f "$sketchdir/.test.skip" ]]; then - continue - fi - echo $sketch >> sketches.txt - sketchnum=$(($sketchnum + 1)) - done - return $sketchnum -} - -function build_pio_sketches() # build_pio_sketches -{ - if [ "$#" -lt 2 ]; then - echo "ERROR: Illegal number of parameters" - echo "USAGE: build_pio_sketches [ ]" - return 1 - fi - - local board=$1 - local examples=$2 - local chunk_idex=$3 - local chunks_num=$4 - - if [ "$#" -lt 4 ]; then - chunk_idex="0" - chunks_num="1" - fi - - if [ "$chunks_num" -le 0 ]; then - echo "ERROR: Chunks count must be positive number" - return 1 - fi - if [ "$chunk_idex" -ge "$chunks_num" ]; then - echo "ERROR: Chunk index must be less than chunks count" - return 1 - fi - - set +e - count_sketches "$examples" - local sketchcount=$? - set -e - local sketches=$(cat sketches.txt) - rm -rf sketches.txt - - local chunk_size=$(( $sketchcount / $chunks_num )) - local all_chunks=$(( $chunks_num * $chunk_size )) - if [ "$all_chunks" -lt "$sketchcount" ]; then - chunk_size=$(( $chunk_size + 1 )) - fi - - local start_index=$(( $chunk_idex * $chunk_size )) - if [ "$sketchcount" -le "$start_index" ]; then - echo "Skipping job" - return 0 - fi - - local end_index=$(( $(( $chunk_idex + 1 )) * $chunk_size )) - if [ "$end_index" -gt "$sketchcount" ]; then - end_index=$sketchcount - fi - - local start_num=$(( $start_index + 1 )) - echo "Found $sketchcount Sketches"; - echo "Chunk Count : $chunks_num" - echo "Chunk Size : $chunk_size" - echo "Start Sketch: $start_num" - echo "End Sketch : $end_index" - - local sketchnum=0 - for sketch in $sketches; do - local sketchdir=$(dirname $sketch) - local sketchdirname=$(basename $sketchdir) - local sketchname=$(basename $sketch) - if [ "${sketchdirname}.ino" != "$sketchname" ] \ - || [ -f "$sketchdir/.test.skip" ]; then - continue - fi - sketchnum=$(($sketchnum + 1)) - if [ "$sketchnum" -le "$start_index" ] \ - || [ "$sketchnum" -gt "$end_index" ]; then - continue - fi - build_pio_sketch "$board" "$sketch" - local result=$? - if [ $result -ne 0 ]; then - return $result - fi - done - return 0 -} diff --git a/.github/scripts/on-push.sh b/.github/scripts/on-push.sh deleted file mode 100755 index ece5d7a..0000000 --- a/.github/scripts/on-push.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -set -e - -if [ ! -z "$TRAVIS_BUILD_DIR" ]; then - export GITHUB_WORKSPACE="$TRAVIS_BUILD_DIR" - export GITHUB_REPOSITORY="$TRAVIS_REPO_SLUG" -elif [ -z "$GITHUB_WORKSPACE" ]; then - export GITHUB_WORKSPACE="$PWD" - export GITHUB_REPOSITORY="me-no-dev/AsyncTCP" -fi - -CHUNK_INDEX=$1 -CHUNKS_CNT=$2 -BUILD_PIO=0 -if [ "$#" -lt 2 ] || [ "$CHUNKS_CNT" -le 0 ]; then - CHUNK_INDEX=0 - CHUNKS_CNT=1 -elif [ "$CHUNK_INDEX" -gt "$CHUNKS_CNT" ]; then - CHUNK_INDEX=$CHUNKS_CNT -elif [ "$CHUNK_INDEX" -eq "$CHUNKS_CNT" ]; then - BUILD_PIO=1 -fi - -if [ "$BUILD_PIO" -eq 0 ]; then - # ArduinoIDE Test - source ./.github/scripts/install-arduino-ide.sh - source ./.github/scripts/install-arduino-core-esp32.sh - - echo "Installing AsyncTCP ..." - cp -rf "$GITHUB_WORKSPACE" "$ARDUINO_USR_PATH/libraries/AsyncTCP" - - FQBN="espressif:esp32:esp32:PSRAM=enabled,PartitionScheme=huge_app" - build_sketches "$FQBN" "$GITHUB_WORKSPACE/examples" - if [ ! "$OS_IS_WINDOWS" == "1" ]; then - echo "Installing ESPAsyncWebServer ..." - git clone https://github.com/me-no-dev/ESPAsyncWebServer "$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer" > /dev/null 2>&1 - - echo "Installing ArduinoJson ..." - git clone https://github.com/bblanchon/ArduinoJson "$ARDUINO_USR_PATH/libraries/ArduinoJson" > /dev/null 2>&1 - - build_sketches "$FQBN" "$ARDUINO_USR_PATH/libraries/ESPAsyncWebServer/examples" - fi -else - # PlatformIO Test - source ./.github/scripts/install-platformio.sh - - echo "Installing AsyncTCP ..." - python -m platformio lib --storage-dir "$GITHUB_WORKSPACE" install - - BOARD="esp32dev" - build_pio_sketches "$BOARD" "$GITHUB_WORKSPACE/examples" - - if [[ "$OSTYPE" != "cygwin" ]] && [[ "$OSTYPE" != "msys" ]] && [[ "$OSTYPE" != "win32" ]]; then - echo "Installing ESPAsyncWebServer ..." - python -m platformio lib -g install https://github.com/me-no-dev/ESPAsyncWebServer.git > /dev/null 2>&1 - git clone https://github.com/me-no-dev/ESPAsyncWebServer "$HOME/ESPAsyncWebServer" > /dev/null 2>&1 - - echo "Installing ArduinoJson ..." - python -m platformio lib -g install https://github.com/bblanchon/ArduinoJson.git > /dev/null 2>&1 - - build_pio_sketches "$BOARD" "$HOME/ESPAsyncWebServer/examples" - fi -fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2e4ae11 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,95 @@ +name: Async TCP CI + +on: + push: + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + build-arduino: + name: ${{ matrix.config }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + config: [arduino-cli.yaml, arduino-cli-dev.yaml] + steps: + - uses: actions/checkout@v4 + - uses: arduino/setup-arduino-cli@v1 + - name: Download board + run: | + arduino-cli --config-file ${{ matrix.config }} core update-index + arduino-cli --config-file ${{ matrix.config }} board listall + arduino-cli --config-file ${{ matrix.config }} core install esp32:esp32 + - name: Compile Sketch + run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino + - name: Compile Sketch with IPv6 + env: + LWIP_IPV6: true + run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino + + platformio: + name: "pio:${{ matrix.env }}:${{ matrix.board }}" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - env: ci-arduino-2 + board: esp32dev + - env: ci-arduino-2 + board: esp32-s2-saola-1 + - env: ci-arduino-2 + board: esp32-s3-devkitc-1 + - env: ci-arduino-2 + board: esp32-c3-devkitc-02 + + - env: ci-arduino-3 + board: esp32dev + - env: ci-arduino-3 + board: esp32-s2-saola-1 + - env: ci-arduino-3 + board: esp32-s3-devkitc-1 + - env: ci-arduino-3 + board: esp32-c3-devkitc-02 + - env: ci-arduino-3 + board: esp32-c6-devkitc-1 + + - env: ci-arduino-311 + board: esp32dev + - env: ci-arduino-311 + board: esp32-s2-saola-1 + - env: ci-arduino-311 + board: esp32-s3-devkitc-1 + - env: ci-arduino-311 + board: esp32-c3-devkitc-02 + - env: ci-arduino-311 + board: esp32-c6-devkitc-1 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + key: ${{ runner.os }}-pio + path: | + ~/.cache/pip + ~/.platformio + + - name: Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + + - name: Build + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - run: PLATFORMIO_SRC_DIR=examples/Client PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 15cd441..0000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Async TCP CI - -on: - push: - branches: - - master - - release/* - pull_request: - -jobs: - - build-arduino: - name: Arduino on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - steps: - - uses: actions/checkout@v1 - - name: Build Tests - run: bash ./.github/scripts/on-push.sh 0 1 - - build-pio: - name: PlatformIO on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - steps: - - uses: actions/checkout@v1 - - name: Build Tests - run: bash ./.github/scripts/on-push.sh 1 1 diff --git a/.gitignore b/.gitignore index 9bea433..18584e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ - .DS_Store +.lh +/.pio +/.vscode + +/logs diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 0000000..29eeb43 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,2 @@ +FROM gitpod/workspace-python-3.11 +USER gitpod diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..2f8a443 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,9 @@ +tasks: + - command: pip install --upgrade pip && pip install -U platformio && platformio run + +image: + file: .gitpod.Dockerfile + +vscode: + extensions: + - shardulm94.trailing-spaces diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index dbfc064..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -sudo: false -language: python -os: - - linux - -git: - depth: false - -stages: - - build - -jobs: - include: - - - name: "Arduino Build" - if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) - stage: build - script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh - - - name: "PlatformIO Build" - if: tag IS blank AND (type = pull_request OR (type = push AND branch = master)) - stage: build - script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh 1 1 - -notifications: - email: - on_success: change - on_failure: change - webhooks: - urls: - - https://webhooks.gitter.im/e/60e65d0c78ea0a920347 - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: false # default: false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..0a5f914 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,129 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +https://sidweb.nl/cms3/en/contact. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/README.ESP32Async.md b/README.ESP32Async.md new file mode 100644 index 0000000..b86bcfe --- /dev/null +++ b/README.ESP32Async.md @@ -0,0 +1,60 @@ +# AsyncTCP + +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Continuous Integration](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml/badge.svg)](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/ESP32Async/library/AsyncTCP.svg)](https://registry.platformio.org/libraries/ESP32Async/AsyncTCP) + +Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY) + +### Async TCP Library for ESP32 Arduino + +This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. + +This library is the base for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) + +## AsyncClient and AsyncServer + +The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. + +## Changes + +- `library.properties` for Arduino IDE users +- Add `CONFIG_ASYNC_TCP_MAX_ACK_TIME` +- Add `CONFIG_ASYNC_TCP_PRIORITY` +- Add `CONFIG_ASYNC_TCP_QUEUE_SIZE` +- Add `setKeepAlive()` +- Arduino 3 / ESP-IDF 5 compatibility +- Better CI +- Better example +- Customizable macros +- Fix for "Required to lock TCPIP core functionality". Ref: https://github.com/ESP32Async/AsyncTCP/issues/27 and https://github.com/espressif/arduino-esp32/issues/10526 +- Fix for "ack timeout 4" client disconnects. +- Fix from https://github.com/me-no-dev/AsyncTCP/pull/173 (partially applied) +- Fix from https://github.com/me-no-dev/AsyncTCP/pull/184 +- IPv6 +- LIBRETINY support +- LibreTuya +- Reduce logging of non critical messages +- Use IPADDR6_INIT() macro to set connecting IPv6 address +- xTaskCreateUniversal function + +## Coordinates + +``` +ESP32Async/AsyncTCP @ ^3.3.2 +``` + +## Important recommendations + +Most of the crashes are caused by improper configuration of the library for the project. +Here are some recommendations to avoid them. + +I personally use the following configuration in my projects: + +```c++ + -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default) + -D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default) + -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default) + -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0) + -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K) +``` diff --git a/README.md b/README.md index 983aabd..f66f505 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# AsyncTCP -[![Build Status](https://travis-ci.org/me-no-dev/AsyncTCP.svg?branch=master)](https://travis-ci.org/me-no-dev/AsyncTCP) ![](https://github.com/me-no-dev/AsyncTCP/workflows/Async%20TCP%20CI/badge.svg) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2f7e4d1df8b446d192cbfec6dc174d2d)](https://www.codacy.com/manual/me-no-dev/AsyncTCP?utm_source=github.com&utm_medium=referral&utm_content=me-no-dev/AsyncTCP&utm_campaign=Badge_Grade) +![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4) -### Async TCP Library for ESP32 Arduino +# Project moved to [ESP32Async](https://github.com/organizations/ESP32Async) organization at [https://github.com/ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP) -[![Join the chat at https://gitter.im/me-no-dev/ESPAsyncWebServer](https://badges.gitter.im/me-no-dev/ESPAsyncWebServer.svg)](https://gitter.im/me-no-dev/ESPAsyncWebServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY) -This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. +Please see the new links: -This library is the base for [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) - -## AsyncClient and AsyncServer -The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use. +- `ESP32Async/ESPAsyncWebServer @ 3.6.0` (ESP32, ESP8266, RP2040) +- `ESP32Async/AsyncTCP @ 3.3.2` (ESP32) +- `ESP32Async/ESPAsyncTCP @ 2.0.0` (ESP8266) +- `https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip` (AsyncTCP alternative for ESP32) +- `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (RP2040) diff --git a/arduino-cli-dev.yaml b/arduino-cli-dev.yaml new file mode 100644 index 0000000..174df7a --- /dev/null +++ b/arduino-cli-dev.yaml @@ -0,0 +1,25 @@ +board_manager: + additional_urls: + - https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json +directories: + builtin.libraries: ./src/ +build_cache: + compilations_before_purge: 10 + ttl: 720h0m0s +daemon: + port: "50051" +library: + enable_unsafe_install: false +logging: + file: "" + format: text + level: info +metrics: + addr: :9090 + enabled: true +output: + no_color: false +sketch: + always_export_binaries: false +updater: + enable_notification: true diff --git a/arduino-cli.yaml b/arduino-cli.yaml new file mode 100644 index 0000000..42365f4 --- /dev/null +++ b/arduino-cli.yaml @@ -0,0 +1,25 @@ +board_manager: + additional_urls: + - https://espressif.github.io/arduino-esp32/package_esp32_index.json +directories: + builtin.libraries: ./src/ +build_cache: + compilations_before_purge: 10 + ttl: 720h0m0s +daemon: + port: "50051" +library: + enable_unsafe_install: false +logging: + file: "" + format: text + level: info +metrics: + addr: :9090 + enabled: true +output: + no_color: false +sketch: + always_export_binaries: false +updater: + enable_notification: true diff --git a/examples/Client/Client.ino b/examples/Client/Client.ino new file mode 100644 index 0000000..609af6d --- /dev/null +++ b/examples/Client/Client.ino @@ -0,0 +1,81 @@ +#include +#include +#include + +// Run a server at the root of the project with: +// > python3 -m http.server 3333 +// Now you can open a browser and test it works by visiting http://192.168.125.122:3333/ or http://192.168.125.122:3333/README.md +#define HOST "192.168.125.122" +#define PORT 3333 + +// WiFi SSID to connect to +#define WIFI_SSID "IoT" + +// 16 slots on esp32 (CONFIG_LWIP_MAX_ACTIVE_TCP) +#define MAX_CLIENTS CONFIG_LWIP_MAX_ACTIVE_TCP +// #define MAX_CLIENTS 1 + +size_t permits = MAX_CLIENTS; + +void makeRequest() { + if (!permits) + return; + + Serial.printf("** permits: %d\n", permits); + + AsyncClient* client = new AsyncClient; + + client->onError([](void* arg, AsyncClient* client, int8_t error) { + Serial.printf("** error occurred %s \n", client->errorToString(error)); + client->close(true); + delete client; + }); + + client->onConnect([](void* arg, AsyncClient* client) { + permits--; + Serial.printf("** client has been connected: %" PRIu16 "\n", client->localPort()); + + client->onDisconnect([](void* arg, AsyncClient* client) { + Serial.printf("** client has been disconnected: %" PRIu16 "\n", client->localPort()); + client->close(true); + delete client; + + permits++; + makeRequest(); + }); + + client->onData([](void* arg, AsyncClient* client, void* data, size_t len) { + Serial.printf("** data received by client: %" PRIu16 ": len=%u\n", client->localPort(), len); + }); + + client->write("GET /README.md HTTP/1.1\r\nHost: " HOST "\r\nUser-Agent: ESP\r\nConnection: close\r\n\r\n"); + }); + + if (client->connect(HOST, PORT)) { + } else { + Serial.println("** connection failed"); + } +} + +void setup() { + Serial.begin(115200); + while (!Serial) + continue; + + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("** connected to WiFi"); + Serial.println(WiFi.localIP()); + + for (size_t i = 0; i < MAX_CLIENTS; i++) + makeRequest(); +} + +void loop() { + delay(1000); + Serial.printf("** free heap: %" PRIu32 "\n", ESP.getFreeHeap()); +} diff --git a/library.json b/library.json index 89f90e4..7e41f19 100644 --- a/library.json +++ b/library.json @@ -1,22 +1,31 @@ { - "name":"AsyncTCP", - "description":"Asynchronous TCP Library for ESP32", - "keywords":"async,tcp", + "name": "AsyncTCP", + "version": "3.3.2", + "description": "Asynchronous TCP Library for ESP32", + "keywords": "async,tcp", + "repository": { + "type": "git", + "url": "https://github.com/ESP32Async/AsyncTCP.git" + }, "authors": { - "name": "Hristo Gochkov", + "name": "ESP32Async", "maintainer": true }, - "repository": - { - "type": "git", - "url": "https://github.com/me-no-dev/AsyncTCP.git" - }, - "version": "1.1.1", "license": "LGPL-3.0", "frameworks": "arduino", - "platforms": "espressif32", - "build": { - "libCompatMode": 2 - } + "platforms": [ + "espressif32", + "libretiny" + ], + "export": { + "include": [ + "examples", + "src", + "library.json", + "library.properties", + "LICENSE", + "README.md" + ] + } } diff --git a/library.properties b/library.properties index eb4e26e..6bde633 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,11 @@ -name=AsyncTCP -version=1.1.1 -author=Me-No-Dev -maintainer=Me-No-Dev +name=Async TCP +includes=AsyncTCP.h +version=3.3.2 +author=ESP32Async +maintainer=ESP32Async sentence=Async TCP Library for ESP32 paragraph=Async TCP Library for ESP32 category=Other -url=https://github.com/me-no-dev/AsyncTCP +url=https://github.com/ESP32Async/AsyncTCP.git architectures=* +license=LGPL-3.0 diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..a19908a --- /dev/null +++ b/platformio.ini @@ -0,0 +1,43 @@ +[platformio] +default_envs = arduino-2, arduino-3, arduino-311 +lib_dir = . +src_dir = examples/Client + +[env] +framework = arduino +build_flags = + -Wall -Wextra + -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 + -D CONFIG_ASYNC_TCP_PRIORITY=10 + -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 + -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 + -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 + -D CONFIG_ARDUHAL_LOG_COLORS + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG +upload_protocol = esptool +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder, log2file +board = esp32dev + +[env:arduino-2] +platform = espressif32@6.9.0 + +[env:arduino-3] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip + +[env:arduino-311] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip + +; CI + +[env:ci-arduino-2] +platform = espressif32@6.9.0 +board = ${sysenv.PIO_BOARD} + +[env:ci-arduino-3] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +board = ${sysenv.PIO_BOARD} + +[env:ci-arduino-311] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip +board = ${sysenv.PIO_BOARD} diff --git a/src/AsyncTCP.cpp b/src/AsyncTCP.cpp index 8e8386c..0761144 100644 --- a/src/AsyncTCP.cpp +++ b/src/AsyncTCP.cpp @@ -22,204 +22,299 @@ #include "Arduino.h" #include "AsyncTCP.h" -extern "C"{ -#include "lwip/opt.h" -#include "lwip/tcp.h" -#include "lwip/inet.h" + +extern "C" { #include "lwip/dns.h" #include "lwip/err.h" +#include "lwip/inet.h" +#include "lwip/opt.h" +#include "lwip/tcp.h" } -#include "esp_task_wdt.h" + +#if CONFIG_ASYNC_TCP_USE_WDT + #include "esp_task_wdt.h" +#endif + +// Required for: +// https://github.com/espressif/arduino-esp32/blob/3.0.3/libraries/Network/src/NetworkInterface.cpp#L37-L47 +#if ESP_IDF_VERSION_MAJOR >= 5 + #include +#endif + +#define TAG "AsyncTCP" + +// https://github.com/espressif/arduino-esp32/issues/10526 +#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING + #define TCP_MUTEX_LOCK() \ + if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ + LOCK_TCPIP_CORE(); \ + } + + #define TCP_MUTEX_UNLOCK() \ + if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ + UNLOCK_TCPIP_CORE(); \ + } +#else // CONFIG_LWIP_TCPIP_CORE_LOCKING + #define TCP_MUTEX_LOCK() + #define TCP_MUTEX_UNLOCK() +#endif // CONFIG_LWIP_TCPIP_CORE_LOCKING + +#define INVALID_CLOSED_SLOT -1 + +/* + TCP poll interval is specified in terms of the TCP coarse timer interval, which is called twice a second + https://github.com/espressif/esp-lwip/blob/2acf959a2bb559313cd2bf9306c24612ba3d0e19/src/core/tcp.c#L1895 +*/ +#define CONFIG_ASYNC_TCP_POLL_TIMER 1 /* * TCP/IP Event Task * */ -// https://github.com/espressif/arduino-esp32/issues/10526 -#ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING -#define TCP_MUTEX_LOCK() \ - if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ - LOCK_TCPIP_CORE(); \ - } - -#define TCP_MUTEX_UNLOCK() \ - if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \ - UNLOCK_TCPIP_CORE(); \ - } -#else // CONFIG_LWIP_TCPIP_CORE_LOCKING -#define TCP_MUTEX_LOCK() -#define TCP_MUTEX_UNLOCK() -#endif // CONFIG_LWIP_TCPIP_CORE_LOCKING - typedef enum { - LWIP_TCP_SENT, LWIP_TCP_RECV, LWIP_TCP_FIN, LWIP_TCP_ERROR, LWIP_TCP_POLL, LWIP_TCP_CLEAR, LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS + LWIP_TCP_SENT, + LWIP_TCP_RECV, + LWIP_TCP_FIN, + LWIP_TCP_ERROR, + LWIP_TCP_POLL, + LWIP_TCP_CLEAR, + LWIP_TCP_ACCEPT, + LWIP_TCP_CONNECTED, + LWIP_TCP_DNS } lwip_event_t; typedef struct { - lwip_event_t event; - void *arg; - union { - struct { - void * pcb; - int8_t err; - } connected; - struct { - int8_t err; - } error; - struct { - tcp_pcb * pcb; - uint16_t len; - } sent; - struct { - tcp_pcb * pcb; - pbuf * pb; - int8_t err; - } recv; - struct { - tcp_pcb * pcb; - int8_t err; - } fin; - struct { - tcp_pcb * pcb; - } poll; - struct { - AsyncClient * client; - } accept; - struct { - const char * name; - ip_addr_t addr; - } dns; - }; + lwip_event_t event; + void* arg; + union { + struct { + tcp_pcb* pcb; + int8_t err; + } connected; + struct { + int8_t err; + } error; + struct { + tcp_pcb* pcb; + uint16_t len; + } sent; + struct { + tcp_pcb* pcb; + pbuf* pb; + int8_t err; + } recv; + struct { + tcp_pcb* pcb; + int8_t err; + } fin; + struct { + tcp_pcb* pcb; + } poll; + struct { + AsyncClient* client; + } accept; + struct { + const char* name; + ip_addr_t addr; + } dns; + }; } lwip_event_packet_t; static QueueHandle_t _async_queue; static TaskHandle_t _async_service_task_handle = NULL; - SemaphoreHandle_t _slots_lock; const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; static uint32_t _closed_slots[_number_of_closed_slots]; static uint32_t _closed_index = []() { - _slots_lock = xSemaphoreCreateBinary(); - xSemaphoreGive(_slots_lock); - for (int i = 0; i < _number_of_closed_slots; ++ i) { - _closed_slots[i] = 1; - } - return 1; + _slots_lock = xSemaphoreCreateBinary(); + xSemaphoreGive(_slots_lock); + for (int i = 0; i < _number_of_closed_slots; ++i) { + _closed_slots[i] = 1; + } + return 1; }(); - -static inline bool _init_async_event_queue(){ - if(!_async_queue){ - _async_queue = xQueueCreate(32, sizeof(lwip_event_packet_t *)); - if(!_async_queue){ - return false; - } +static inline bool _init_async_event_queue() { + if (!_async_queue) { + _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE_SIZE, sizeof(lwip_event_packet_t*)); + if (!_async_queue) { + return false; } - return true; + } + return true; } -static inline bool _send_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueSend(_async_queue, e, portMAX_DELAY) == pdPASS; +static inline bool _send_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) { + return _async_queue && xQueueSend(_async_queue, e, wait) == pdPASS; } -static inline bool _prepend_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueSendToFront(_async_queue, e, portMAX_DELAY) == pdPASS; +static inline bool _prepend_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) { + return _async_queue && xQueueSendToFront(_async_queue, e, wait) == pdPASS; } -static inline bool _get_async_event(lwip_event_packet_t ** e){ - return _async_queue && xQueueReceive(_async_queue, e, portMAX_DELAY) == pdPASS; -} +static inline bool _get_async_event(lwip_event_packet_t** e) { + if (!_async_queue) { + return false; + } -static bool _remove_events_with_arg(void * arg){ - lwip_event_packet_t * first_packet = NULL; - lwip_event_packet_t * packet = NULL; - - if(!_async_queue){ - return false; - } - //figure out which is the first packet so we can keep the order - while(!first_packet){ - if(xQueueReceive(_async_queue, &first_packet, 0) != pdPASS){ - return false; - } - //discard packet if matching - if((int)first_packet->arg == (int)arg){ - free(first_packet); - first_packet = NULL; - //return first packet to the back of the queue - } else if(xQueueSend(_async_queue, &first_packet, portMAX_DELAY) != pdPASS){ - return false; - } - } - - while(xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet){ - if(xQueueReceive(_async_queue, &packet, 0) != pdPASS){ - return false; - } - if((int)packet->arg == (int)arg){ - free(packet); - packet = NULL; - } else if(xQueueSend(_async_queue, &packet, portMAX_DELAY) != pdPASS){ - return false; - } - } - return true; -} - -static void _handle_async_event(lwip_event_packet_t * e){ - if(e->arg == NULL){ - // do nothing when arg is NULL - //ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); - } else if(e->event == LWIP_TCP_CLEAR){ - _remove_events_with_arg(e->arg); - } else if(e->event == LWIP_TCP_RECV){ - //ets_printf("-R: 0x%08x\n", e->recv.pcb); - AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); - } else if(e->event == LWIP_TCP_FIN){ - //ets_printf("-F: 0x%08x\n", e->fin.pcb); - AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); - } else if(e->event == LWIP_TCP_SENT){ - //ets_printf("-S: 0x%08x\n", e->sent.pcb); - AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); - } else if(e->event == LWIP_TCP_POLL){ - //ets_printf("-P: 0x%08x\n", e->poll.pcb); - AsyncClient::_s_poll(e->arg, e->poll.pcb); - } else if(e->event == LWIP_TCP_ERROR){ - //ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); - AsyncClient::_s_error(e->arg, e->error.err); - } else if(e->event == LWIP_TCP_CONNECTED){ - //ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); - AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); - } else if(e->event == LWIP_TCP_ACCEPT){ - //ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); - AsyncServer::_s_accepted(e->arg, e->accept.client); - } else if(e->event == LWIP_TCP_DNS){ - //ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); - AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); - } - free((void*)(e)); -} - -static void _async_service_task(void *pvParameters){ - lwip_event_packet_t * packet = NULL; - for (;;) { - if(_get_async_event(&packet)){ #if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_add(NULL) != ESP_OK){ - log_e("Failed to add async task to WDT"); - } + // need to return periodically to feed the dog + if (xQueueReceive(_async_queue, e, pdMS_TO_TICKS(1000)) != pdPASS) + return false; +#else + if (xQueueReceive(_async_queue, e, portMAX_DELAY) != pdPASS) + return false; #endif - _handle_async_event(packet); -#if CONFIG_ASYNC_TCP_USE_WDT - if(esp_task_wdt_delete(NULL) != ESP_OK){ - log_e("Failed to remove loop task from WDT"); - } -#endif - } + + if ((*e)->event != LWIP_TCP_POLL) + return true; + + /* + Let's try to coalesce two (or more) consecutive poll events into one + this usually happens with poor implemented user-callbacks that are runs too long and makes poll events to stack in the queue + if consecutive user callback for a same connection runs longer that poll time then it will fill the queue with events until it deadlocks. + This is a workaround to mitigate such poor designs and won't let other events/connections to starve the task time. + It won't be effective if user would run multiple simultaneous long running callbacks due to message interleaving. + todo: implement some kind of fair dequeing or (better) simply punish user for a bad designed callbacks by resetting hog connections + */ + lwip_event_packet_t* next_pkt = NULL; + while (xQueuePeek(_async_queue, &next_pkt, 0) == pdPASS) { + if (next_pkt->arg == (*e)->arg && next_pkt->event == LWIP_TCP_POLL) { + if (xQueueReceive(_async_queue, &next_pkt, 0) == pdPASS) { + free(next_pkt); + next_pkt = NULL; + log_d("coalescing polls, network congestion or async callbacks might be too slow!"); + continue; + } } - vTaskDelete(NULL); - _async_service_task_handle = NULL; + + // quit while loop if next event can't be discarded + break; + } + + /* + now we have to decide if to proceed with poll callback handler or discard it? + poor designed apps using asynctcp without proper dataflow control could flood the queue with interleaved pool/ack events. + I.e. on each poll app would try to generate more data to send, which in turn results in additional ack event triggering chain effect + for long connections. Or poll callback could take long time starving other connections. Anyway our goal is to keep the queue length + grows under control (if possible) and poll events are the safest to discard. + Let's discard poll events processing using linear-increasing probability curve when queue size grows over 3/4 + Poll events are periodic and connection could get another chance next time + */ + if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 4 + CONFIG_ASYNC_TCP_QUEUE_SIZE * 3 / 4)) { + free(*e); + *e = NULL; + log_d("discarding poll due to queue congestion"); + // evict next event from a queue + return _get_async_event(e); + } + + // last resort return + return true; +} + +static bool _remove_events_with_arg(void* arg) { + if (!_async_queue) { + return false; + } + + lwip_event_packet_t* first_packet = NULL; + lwip_event_packet_t* packet = NULL; + + // figure out which is the first non-matching packet so we can keep the order + while (!first_packet) { + if (xQueueReceive(_async_queue, &first_packet, 0) != pdPASS) { + return false; + } + // discard packet if matching + if ((int)first_packet->arg == (int)arg) { + free(first_packet); + first_packet = NULL; + } else if (xQueueSend(_async_queue, &first_packet, 0) != pdPASS) { + // try to return first packet to the back of the queue + // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue + // otherwise it would deadlock, we have to discard the event + free(first_packet); + first_packet = NULL; + return false; + } + } + + while (xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet) { + if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) { + return false; + } + if ((int)packet->arg == (int)arg) { + // remove matching event + free(packet); + packet = NULL; + // otherwise try to requeue it + } else if (xQueueSend(_async_queue, &packet, 0) != pdPASS) { + // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue + // otherwise it would deadlock, we have to discard the event + free(packet); + packet = NULL; + return false; + } + } + return true; +} + +static void _handle_async_event(lwip_event_packet_t* e) { + if (e->arg == NULL) { + // do nothing when arg is NULL + // ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb); + } else if (e->event == LWIP_TCP_CLEAR) { + _remove_events_with_arg(e->arg); + } else if (e->event == LWIP_TCP_RECV) { + // ets_printf("-R: 0x%08x\n", e->recv.pcb); + AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err); + } else if (e->event == LWIP_TCP_FIN) { + // ets_printf("-F: 0x%08x\n", e->fin.pcb); + AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err); + } else if (e->event == LWIP_TCP_SENT) { + // ets_printf("-S: 0x%08x\n", e->sent.pcb); + AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len); + } else if (e->event == LWIP_TCP_POLL) { + // ets_printf("-P: 0x%08x\n", e->poll.pcb); + AsyncClient::_s_poll(e->arg, e->poll.pcb); + } else if (e->event == LWIP_TCP_ERROR) { + // ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err); + AsyncClient::_s_error(e->arg, e->error.err); + } else if (e->event == LWIP_TCP_CONNECTED) { + // ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err); + AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err); + } else if (e->event == LWIP_TCP_ACCEPT) { + // ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client); + AsyncServer::_s_accepted(e->arg, e->accept.client); + } else if (e->event == LWIP_TCP_DNS) { + // ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr)); + AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg); + } + free((void*)(e)); +} + +static void _async_service_task(void* pvParameters) { +#if CONFIG_ASYNC_TCP_USE_WDT + if (esp_task_wdt_add(NULL) != ESP_OK) { + log_w("Failed to add async task to WDT"); + } +#endif + lwip_event_packet_t* packet = NULL; + for (;;) { + if (_get_async_event(&packet)) { + _handle_async_event(packet); + } +#if CONFIG_ASYNC_TCP_USE_WDT + esp_task_wdt_reset(); +#endif + } +#if CONFIG_ASYNC_TCP_USE_WDT + esp_task_wdt_delete(NULL); +#endif + vTaskDelete(NULL); + _async_service_task_handle = NULL; } /* static void _stop_async_task(){ @@ -229,131 +324,159 @@ static void _stop_async_task(){ } } */ -static bool _start_async_task(){ - if(!_init_async_event_queue()){ - return false; + +static bool customTaskCreateUniversal( + TaskFunction_t pxTaskCode, + const char* const pcName, + const uint32_t usStackDepth, + void* const pvParameters, + UBaseType_t uxPriority, + TaskHandle_t* const pxCreatedTask, + const BaseType_t xCoreID) { +#ifndef CONFIG_FREERTOS_UNICORE + if (xCoreID >= 0 && xCoreID < 2) { + return xTaskCreatePinnedToCore(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, xCoreID); + } else { +#endif + return xTaskCreate(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask); +#ifndef CONFIG_FREERTOS_UNICORE + } +#endif +} + +static bool _start_async_task() { + if (!_init_async_event_queue()) { + return false; + } + if (!_async_service_task_handle) { + customTaskCreateUniversal(_async_service_task, "async_tcp", CONFIG_ASYNC_TCP_STACK_SIZE, NULL, CONFIG_ASYNC_TCP_PRIORITY, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); + if (!_async_service_task_handle) { + return false; } - if(!_async_service_task_handle){ - xTaskCreateUniversal(_async_service_task, "async_tcp", 8192 * 2, NULL, 3, &_async_service_task_handle, CONFIG_ASYNC_TCP_RUNNING_CORE); - if(!_async_service_task_handle){ - return false; - } - } - return true; + } + return true; } /* * LwIP Callbacks * */ -static int8_t _tcp_clear_events(void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CLEAR; - e->arg = arg; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } +static int8_t _tcp_clear_events(void* arg) { + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_CLEAR; + e->arg = arg; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_connected(void* arg, tcp_pcb* pcb, int8_t err) { + // ets_printf("+C: 0x%08x\n", pcb); + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_CONNECTED; + e->arg = arg; + e->connected.pcb = pcb; + e->connected.err = err; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; +} + +static int8_t _tcp_poll(void* arg, struct tcp_pcb* pcb) { + // throttle polling events queing when event queue is getting filled up, let it handle _onack's + // log_d("qs:%u", uxQueueMessagesWaiting(_async_queue)); + if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 2 + CONFIG_ASYNC_TCP_QUEUE_SIZE / 4)) { + log_d("throttling"); return ERR_OK; + } + + // ets_printf("+P: 0x%08x\n", pcb); + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_POLL; + e->arg = arg; + e->poll.pcb = pcb; + // poll events are not critical 'cause those are repetitive, so we may not wait the queue in any case + if (!_send_async_event(&e, 0)) { + free((void*)(e)); + } + return ERR_OK; } -static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) { - //ets_printf("+C: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_CONNECTED; - e->arg = arg; - e->connected.pcb = pcb; - e->connected.err = err; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; +static int8_t _tcp_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) { + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->arg = arg; + if (pb) { + // ets_printf("+R: 0x%08x\n", pcb); + e->event = LWIP_TCP_RECV; + e->recv.pcb = pcb; + e->recv.pb = pb; + e->recv.err = err; + } else { + // ets_printf("+F: 0x%08x\n", pcb); + e->event = LWIP_TCP_FIN; + e->fin.pcb = pcb; + e->fin.err = err; + // close the PCB in LwIP thread + AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); + } + if (!_send_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; } -static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) { - //ets_printf("+P: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_POLL; - e->arg = arg; - e->poll.pcb = pcb; - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; +static int8_t _tcp_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) { + // ets_printf("+S: 0x%08x\n", pcb); + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_SENT; + e->arg = arg; + e->sent.pcb = pcb; + e->sent.len = len; + if (!_send_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; } -static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->arg = arg; - if(pb){ - //ets_printf("+R: 0x%08x\n", pcb); - e->event = LWIP_TCP_RECV; - e->recv.pcb = pcb; - e->recv.pb = pb; - e->recv.err = err; - } else { - //ets_printf("+F: 0x%08x\n", pcb); - e->event = LWIP_TCP_FIN; - e->fin.pcb = pcb; - e->fin.err = err; - //close the PCB in LwIP thread - AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); - } - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; +static void _tcp_error(void* arg, int8_t err) { + // ets_printf("+E: 0x%08x\n", arg); + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_ERROR; + e->arg = arg; + e->error.err = err; + if (!_send_async_event(&e)) { + free((void*)(e)); + } } -static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - //ets_printf("+S: 0x%08x\n", pcb); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_SENT; - e->arg = arg; - e->sent.pcb = pcb; - e->sent.len = len; - if (!_send_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; +static void _tcp_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) { + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + // ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); + e->event = LWIP_TCP_DNS; + e->arg = arg; + e->dns.name = name; + if (ipaddr) { + memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); + } else { + memset(&e->dns.addr, 0, sizeof(e->dns.addr)); + } + if (!_send_async_event(&e)) { + free((void*)(e)); + } } -static void _tcp_error(void * arg, int8_t err) { - //ets_printf("+E: 0x%08x\n", arg); - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ERROR; - e->arg = arg; - e->error.err = err; - if (!_send_async_event(&e)) { - free((void*)(e)); - } -} - -static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, void * arg) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - //ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); - e->event = LWIP_TCP_DNS; - e->arg = arg; - e->dns.name = name; - if (ipaddr) { - memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr)); - } else { - memset(&e->dns.addr, 0, sizeof(e->dns.addr)); - } - if (!_send_async_event(&e)) { - free((void*)(e)); - } -} - -//Used to switch out from LwIP thread -static int8_t _tcp_accept(void * arg, AsyncClient * client) { - lwip_event_packet_t * e = (lwip_event_packet_t *)malloc(sizeof(lwip_event_packet_t)); - e->event = LWIP_TCP_ACCEPT; - e->arg = arg; - e->accept.client = client; - if (!_prepend_async_event(&e)) { - free((void*)(e)); - } - return ERR_OK; +// Used to switch out from LwIP thread +static int8_t _tcp_accept(void* arg, AsyncClient* client) { + lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t)); + e->event = LWIP_TCP_ACCEPT; + e->arg = arg; + e->accept.client = client; + if (!_prepend_async_event(&e)) { + free((void*)(e)); + } + return ERR_OK; } /* @@ -364,447 +487,458 @@ static int8_t _tcp_accept(void * arg, AsyncClient * client) { typedef struct { struct tcpip_api_call_data call; - tcp_pcb * pcb; + tcp_pcb* pcb; int8_t closed_slot; int8_t err; union { - struct { - const char* data; - size_t size; - uint8_t apiflags; - } write; - size_t received; - struct { - ip_addr_t * addr; - uint16_t port; - tcp_connected_fn cb; - } connect; - struct { - ip_addr_t * addr; - uint16_t port; - } bind; - uint8_t backlog; + struct { + const char* data; + size_t size; + uint8_t apiflags; + } write; + size_t received; + struct { + ip_addr_t* addr; + uint16_t port; + tcp_connected_fn cb; + } connect; + struct { + ip_addr_t* addr; + uint16_t port; + } bind; + uint8_t backlog; }; } tcp_api_call_t; -static err_t _tcp_output_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_output(msg->pcb); - } - return msg->err; +static err_t _tcp_output_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = ERR_CONN; + if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_output(msg->pcb); + } + return msg->err; } -static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); - return msg.err; +static esp_err_t _tcp_output(tcp_pcb* pcb, int8_t closed_slot) { + if (!pcb) { + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg); + return msg.err; } -static err_t _tcp_write_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); - } - return msg->err; +static err_t _tcp_write_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = ERR_CONN; + if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags); + } + return msg->err; } -static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.write.data = data; - msg.write.size = size; - msg.write.apiflags = apiflags; - tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); - return msg.err; +static esp_err_t _tcp_write(tcp_pcb* pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) { + if (!pcb) { + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.write.data = data; + msg.write.size = size; + msg.write.apiflags = apiflags; + tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); + return msg.err; } -static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = 0; - tcp_recved(msg->pcb, msg->received); - } - return msg->err; -} - -static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t len) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.received = len; - tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_close_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - msg->err = tcp_close(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_abort_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = ERR_CONN; - if(msg->closed_slot == -1 || !_closed_slots[msg->closed_slot]) { - tcp_abort(msg->pcb); - } - return msg->err; -} - -static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) { - if(!pcb){ - return ERR_CONN; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_connect_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); - return msg->err; -} - -static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr_t * addr, uint16_t port, tcp_connected_fn cb) { - if(!pcb){ - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = closed_slot; - msg.connect.addr = addr; - msg.connect.port = port; - msg.connect.cb = cb; - tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_bind_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; - msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); - return msg->err; -} - -static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t port) { - if(!pcb){ - return ESP_FAIL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.bind.addr = addr; - msg.bind.port = port; - tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); - return msg.err; -} - -static err_t _tcp_listen_api(struct tcpip_api_call_data *api_call_msg){ - tcp_api_call_t * msg = (tcp_api_call_t *)api_call_msg; +static err_t _tcp_recved_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = ERR_CONN; + if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { + // if(msg->closed_slot != INVALID_CLOSED_SLOT && !_closed_slots[msg->closed_slot]) { + // if(msg->closed_slot != INVALID_CLOSED_SLOT) { msg->err = 0; - msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); - return msg->err; + tcp_recved(msg->pcb, msg->received); + } + return msg->err; } -static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) { - if(!pcb){ - return NULL; - } - tcp_api_call_t msg; - msg.pcb = pcb; - msg.closed_slot = -1; - msg.backlog = backlog?backlog:0xFF; - tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); - return msg.pcb; +static esp_err_t _tcp_recved(tcp_pcb* pcb, int8_t closed_slot, size_t len) { + if (!pcb) { + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.received = len; + tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); + return msg.err; } +static err_t _tcp_close_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = ERR_CONN; + if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { + msg->err = tcp_close(msg->pcb); + } + return msg->err; +} +static esp_err_t _tcp_close(tcp_pcb* pcb, int8_t closed_slot) { + if (!pcb) { + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_abort_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = ERR_CONN; + if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) { + tcp_abort(msg->pcb); + } + return msg->err; +} + +static esp_err_t _tcp_abort(tcp_pcb* pcb, int8_t closed_slot) { + if (!pcb) { + return ERR_CONN; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_connect_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb); + return msg->err; +} + +static esp_err_t _tcp_connect(tcp_pcb* pcb, int8_t closed_slot, ip_addr_t* addr, uint16_t port, tcp_connected_fn cb) { + if (!pcb) { + return ESP_FAIL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = closed_slot; + msg.connect.addr = addr; + msg.connect.port = port; + msg.connect.cb = cb; + tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_bind_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port); + return msg->err; +} + +static esp_err_t _tcp_bind(tcp_pcb* pcb, ip_addr_t* addr, uint16_t port) { + if (!pcb) { + return ESP_FAIL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = -1; + msg.bind.addr = addr; + msg.bind.port = port; + tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); + return msg.err; +} + +static err_t _tcp_listen_api(struct tcpip_api_call_data* api_call_msg) { + tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg; + msg->err = 0; + msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog); + return msg->err; +} + +static tcp_pcb* _tcp_listen_with_backlog(tcp_pcb* pcb, uint8_t backlog) { + if (!pcb) { + return NULL; + } + tcp_api_call_t msg; + msg.pcb = pcb; + msg.closed_slot = -1; + msg.backlog = backlog ? backlog : 0xFF; + tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); + return msg.pcb; +} /* Async TCP Client */ AsyncClient::AsyncClient(tcp_pcb* pcb) -: _connect_cb(0) -, _connect_cb_arg(0) -, _discard_cb(0) -, _discard_cb_arg(0) -, _sent_cb(0) -, _sent_cb_arg(0) -, _error_cb(0) -, _error_cb_arg(0) -, _recv_cb(0) -, _recv_cb_arg(0) -, _pb_cb(0) -, _pb_cb_arg(0) -, _timeout_cb(0) -, _timeout_cb_arg(0) -, _pcb_busy(false) -, _pcb_sent_at(0) -, _ack_pcb(true) -, _rx_last_packet(0) -, _rx_since_timeout(0) -, _ack_timeout(ASYNC_MAX_ACK_TIME) -, _connect_port(0) -, prev(NULL) -, next(NULL) -{ - _pcb = pcb; - _closed_slot = -1; - if(_pcb){ - _allocate_closed_slot(); - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); + : _connect_cb(0), _connect_cb_arg(0), _discard_cb(0), _discard_cb_arg(0), _sent_cb(0), _sent_cb_arg(0), _error_cb(0), _error_cb_arg(0), _recv_cb(0), _recv_cb_arg(0), _pb_cb(0), _pb_cb_arg(0), _timeout_cb(0), _timeout_cb_arg(0), _ack_pcb(true), _tx_last_packet(0), _rx_timeout(0), _rx_last_ack(0), _ack_timeout(CONFIG_ASYNC_TCP_MAX_ACK_TIME), _connect_port(0), prev(NULL), next(NULL) { + _pcb = pcb; + _closed_slot = INVALID_CLOSED_SLOT; + if (_pcb) { + _rx_last_packet = millis(); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_tcp_recv); + tcp_sent(_pcb, &_tcp_sent); + tcp_err(_pcb, &_tcp_error); + tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); + if (!_allocate_closed_slot()) { + _close(); } + } } -AsyncClient::~AsyncClient(){ - if(_pcb) { - _close(); - } - _free_closed_slot(); +AsyncClient::~AsyncClient() { + if (_pcb) { + _close(); + } + _free_closed_slot(); } /* * Operators * */ -AsyncClient& AsyncClient::operator=(const AsyncClient& other){ - if (_pcb) { - _close(); - } +AsyncClient& AsyncClient::operator=(const AsyncClient& other) { + if (_pcb) { + _close(); + } - _pcb = other._pcb; - _closed_slot = other._closed_slot; - if (_pcb) { - _rx_last_packet = millis(); - tcp_arg(_pcb, this); - tcp_recv(_pcb, &_tcp_recv); - tcp_sent(_pcb, &_tcp_sent); - tcp_err(_pcb, &_tcp_error); - tcp_poll(_pcb, &_tcp_poll, 1); - } - return *this; + _pcb = other._pcb; + _closed_slot = other._closed_slot; + if (_pcb) { + _rx_last_packet = millis(); + tcp_arg(_pcb, this); + tcp_recv(_pcb, &_tcp_recv); + tcp_sent(_pcb, &_tcp_sent); + tcp_err(_pcb, &_tcp_error); + tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); + } + return *this; } -bool AsyncClient::operator==(const AsyncClient &other) { - return _pcb == other._pcb; +bool AsyncClient::operator==(const AsyncClient& other) { + return _pcb == other._pcb; } -AsyncClient & AsyncClient::operator+=(const AsyncClient &other) { - if(next == NULL){ - next = (AsyncClient*)(&other); - next->prev = this; - } else { - AsyncClient *c = next; - while(c->next != NULL) { - c = c->next; - } - c->next =(AsyncClient*)(&other); - c->next->prev = c; +AsyncClient& AsyncClient::operator+=(const AsyncClient& other) { + if (next == NULL) { + next = (AsyncClient*)(&other); + next->prev = this; + } else { + AsyncClient* c = next; + while (c->next != NULL) { + c = c->next; } - return *this; + c->next = (AsyncClient*)(&other); + c->next->prev = c; + } + return *this; } /* * Callback Setters * */ -void AsyncClient::onConnect(AcConnectHandler cb, void* arg){ - _connect_cb = cb; - _connect_cb_arg = arg; +void AsyncClient::onConnect(AcConnectHandler cb, void* arg) { + _connect_cb = cb; + _connect_cb_arg = arg; } -void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg){ - _discard_cb = cb; - _discard_cb_arg = arg; +void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg) { + _discard_cb = cb; + _discard_cb_arg = arg; } -void AsyncClient::onAck(AcAckHandler cb, void* arg){ - _sent_cb = cb; - _sent_cb_arg = arg; +void AsyncClient::onAck(AcAckHandler cb, void* arg) { + _sent_cb = cb; + _sent_cb_arg = arg; } -void AsyncClient::onError(AcErrorHandler cb, void* arg){ - _error_cb = cb; - _error_cb_arg = arg; +void AsyncClient::onError(AcErrorHandler cb, void* arg) { + _error_cb = cb; + _error_cb_arg = arg; } -void AsyncClient::onData(AcDataHandler cb, void* arg){ - _recv_cb = cb; - _recv_cb_arg = arg; +void AsyncClient::onData(AcDataHandler cb, void* arg) { + _recv_cb = cb; + _recv_cb_arg = arg; } -void AsyncClient::onPacket(AcPacketHandler cb, void* arg){ +void AsyncClient::onPacket(AcPacketHandler cb, void* arg) { _pb_cb = cb; _pb_cb_arg = arg; } -void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg){ - _timeout_cb = cb; - _timeout_cb_arg = arg; +void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg) { + _timeout_cb = cb; + _timeout_cb_arg = arg; } -void AsyncClient::onPoll(AcConnectHandler cb, void* arg){ - _poll_cb = cb; - _poll_cb_arg = arg; +void AsyncClient::onPoll(AcConnectHandler cb, void* arg) { + _poll_cb = cb; + _poll_cb_arg = arg; } /* * Main Public Methods * */ -bool AsyncClient::connect(IPAddress ip, uint16_t port){ - if (_pcb){ - log_w("already connected, state %d", _pcb->state); - return false; - } - if(!_start_async_task()){ - log_e("failed to start task"); - return false; - } - - ip_addr_t addr; - addr.type = IPADDR_TYPE_V4; - addr.u_addr.ip4.addr = ip; - - TCP_MUTEX_LOCK(); - tcp_pcb* pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - if (!pcb){ - TCP_MUTEX_UNLOCK(); - log_e("pcb == NULL"); - return false; - } - - tcp_arg(pcb, this); - tcp_err(pcb, &_tcp_error); - tcp_recv(pcb, &_tcp_recv); - tcp_sent(pcb, &_tcp_sent); - tcp_poll(pcb, &_tcp_poll, 1); - TCP_MUTEX_UNLOCK(); - //_tcp_connect(pcb, &addr, port,(tcp_connected_fn)&_s_connected); - esp_err_t err = _tcp_connect(pcb, _closed_slot, &addr, port,(tcp_connected_fn)&_tcp_connected); - return err == ESP_OK; -} - -bool AsyncClient::connect(const char* host, uint16_t port){ - ip_addr_t addr; - - if(!_start_async_task()){ - log_e("failed to start task"); - return false; - } - TCP_MUTEX_LOCK(); - err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); - TCP_MUTEX_UNLOCK(); - if(err == ERR_OK) { - return connect(IPAddress(addr.u_addr.ip4.addr), port); - } else if(err == ERR_INPROGRESS) { - _connect_port = port; - return true; - } - log_e("error: %d", err); +bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { + if (_pcb) { + log_d("already connected, state %d", _pcb->state); return false; + } + if (!_start_async_task()) { + log_e("failed to start task"); + return false; + } + + if (!_allocate_closed_slot()) { + log_e("failed to allocate: closed slot full"); + return false; + } + + TCP_MUTEX_LOCK(); + tcp_pcb* pcb = tcp_new_ip_type(addr.type); + if (!pcb) { + TCP_MUTEX_UNLOCK(); + log_e("pcb == NULL"); + return false; + } + tcp_arg(pcb, this); + tcp_err(pcb, &_tcp_error); + tcp_recv(pcb, &_tcp_recv); + tcp_sent(pcb, &_tcp_sent); + tcp_poll(pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER); + TCP_MUTEX_UNLOCK(); + + esp_err_t err = _tcp_connect(pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected); + return err == ESP_OK; } -void AsyncClient::close(bool now){ - if(_pcb){ - _tcp_recved(_pcb, _closed_slot, _rx_ack_len); - } - _close(); +bool AsyncClient::connect(const IPAddress& ip, uint16_t port) { + ip_addr_t addr; +#if ESP_IDF_VERSION_MAJOR < 5 + addr.u_addr.ip4.addr = ip; + addr.type = IPADDR_TYPE_V4; +#else + ip.to_ip_addr_t(&addr); +#endif + + return _connect(addr, port); } -int8_t AsyncClient::abort(){ - if(_pcb) { - _tcp_abort(_pcb, _closed_slot ); - _pcb = NULL; +#if LWIP_IPV6 && ESP_IDF_VERSION_MAJOR < 5 +bool AsyncClient::connect(const IPv6Address& ip, uint16_t port) { + auto ipaddr = static_cast(ip); + ip_addr_t addr = IPADDR6_INIT(ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]); + + return _connect(addr, port); +} +#endif + +bool AsyncClient::connect(const char* host, uint16_t port) { + ip_addr_t addr; + + if (!_start_async_task()) { + log_e("failed to start task"); + return false; + } + + TCP_MUTEX_LOCK(); + err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this); + TCP_MUTEX_UNLOCK(); + if (err == ERR_OK) { +#if ESP_IDF_VERSION_MAJOR < 5 + #if LWIP_IPV6 + if (addr.type == IPADDR_TYPE_V6) { + return connect(IPv6Address(addr.u_addr.ip6.addr), port); } - return ERR_ABRT; + return connect(IPAddress(addr.u_addr.ip4.addr), port); + #else + return connect(IPAddress(addr.addr), port); + #endif +#else + return _connect(addr, port); +#endif + } else if (err == ERR_INPROGRESS) { + _connect_port = port; + return true; + } + log_d("error: %d", err); + return false; } -size_t AsyncClient::space(){ - if((_pcb != NULL) && (_pcb->state == 4)){ - return tcp_sndbuf(_pcb); - } - return 0; +void AsyncClient::close(bool now) { + if (_pcb) { + _tcp_recved(_pcb, _closed_slot, _rx_ack_len); + } + _close(); +} + +int8_t AsyncClient::abort() { + if (_pcb) { + _tcp_abort(_pcb, _closed_slot); + _pcb = NULL; + } + return ERR_ABRT; +} + +size_t AsyncClient::space() { + if ((_pcb != NULL) && (_pcb->state == ESTABLISHED)) { + return tcp_sndbuf(_pcb); + } + return 0; } size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { - if(!_pcb || size == 0 || data == NULL) { - return 0; - } - size_t room = space(); - if(!room) { - return 0; - } - size_t will_send = (room < size) ? room : size; - int8_t err = ERR_OK; - err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); - if(err != ERR_OK) { - return 0; - } - return will_send; + if (!_pcb || size == 0 || data == NULL) { + return 0; + } + size_t room = space(); + if (!room) { + return 0; + } + size_t will_send = (room < size) ? room : size; + int8_t err = ERR_OK; + err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags); + if (err != ERR_OK) { + return 0; + } + return will_send; } -bool AsyncClient::send(){ - int8_t err = ERR_OK; - err = _tcp_output(_pcb, _closed_slot); - if(err == ERR_OK){ - _pcb_busy = true; - _pcb_sent_at = millis(); - return true; - } - return false; +bool AsyncClient::send() { + auto backup = _tx_last_packet; + _tx_last_packet = millis(); + if (_tcp_output(_pcb, _closed_slot) == ERR_OK) { + return true; + } + _tx_last_packet = backup; + return false; } -size_t AsyncClient::ack(size_t len){ - if(len > _rx_ack_len) - len = _rx_ack_len; - if(len){ - _tcp_recved(_pcb, _closed_slot, len); - } - _rx_ack_len -= len; - return len; +size_t AsyncClient::ack(size_t len) { + if (len > _rx_ack_len) + len = _rx_ack_len; + if (len) { + _tcp_recved(_pcb, _closed_slot, len); + } + _rx_ack_len -= len; + return len; } -void AsyncClient::ackPacket(struct pbuf * pb){ - if(!pb){ +void AsyncClient::ackPacket(struct pbuf* pb) { + if (!pb) { return; } _tcp_recved(_pcb, _closed_slot, pb->len); @@ -815,441 +949,562 @@ void AsyncClient::ackPacket(struct pbuf * pb){ * Main Private Methods * */ -int8_t AsyncClient::_close(){ - //ets_printf("X: 0x%08x\n", (uint32_t)this); - int8_t err = ERR_OK; - if(_pcb) { - //log_i(""); - TCP_MUTEX_LOCK(); - tcp_arg(_pcb, NULL); - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - TCP_MUTEX_UNLOCK(); - _tcp_clear_events(this); - err = _tcp_close(_pcb, _closed_slot); - if(err != ERR_OK) { - err = abort(); - } - _pcb = NULL; - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } +int8_t AsyncClient::_close() { + // ets_printf("X: 0x%08x\n", (uint32_t)this); + int8_t err = ERR_OK; + if (_pcb) { + TCP_MUTEX_LOCK(); + tcp_arg(_pcb, NULL); + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); + TCP_MUTEX_UNLOCK(); + _tcp_clear_events(this); + err = _tcp_close(_pcb, _closed_slot); + if (err != ERR_OK) { + err = abort(); } - return err; + _free_closed_slot(); + _pcb = NULL; + if (_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + } + return err; } -void AsyncClient::_allocate_closed_slot(){ - xSemaphoreTake(_slots_lock, portMAX_DELAY); - uint32_t closed_slot_min_index = 0; - for (int i = 0; i < _number_of_closed_slots; ++ i) { - if ((_closed_slot == -1 || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { - closed_slot_min_index = _closed_slots[i]; - _closed_slot = i; - } +bool AsyncClient::_allocate_closed_slot() { + if (_closed_slot != INVALID_CLOSED_SLOT) { + return true; + } + xSemaphoreTake(_slots_lock, portMAX_DELAY); + uint32_t closed_slot_min_index = 0; + for (int i = 0; i < _number_of_closed_slots; ++i) { + if ((_closed_slot == INVALID_CLOSED_SLOT || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) { + closed_slot_min_index = _closed_slots[i]; + _closed_slot = i; } - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = 0; - } - xSemaphoreGive(_slots_lock); + } + if (_closed_slot != INVALID_CLOSED_SLOT) { + _closed_slots[_closed_slot] = 0; + } + xSemaphoreGive(_slots_lock); + return (_closed_slot != INVALID_CLOSED_SLOT); } -void AsyncClient::_free_closed_slot(){ - if (_closed_slot != -1) { - _closed_slots[_closed_slot] = _closed_index; - _closed_slot = -1; - ++ _closed_index; - } +void AsyncClient::_free_closed_slot() { + xSemaphoreTake(_slots_lock, portMAX_DELAY); + if (_closed_slot != INVALID_CLOSED_SLOT) { + _closed_slots[_closed_slot] = _closed_index; + _closed_slot = INVALID_CLOSED_SLOT; + ++_closed_index; + } + xSemaphoreGive(_slots_lock); } /* * Private Callbacks * */ -int8_t AsyncClient::_connected(void* pcb, int8_t err){ - _pcb = reinterpret_cast(pcb); - if(_pcb){ - _rx_last_packet = millis(); - _pcb_busy = false; -// tcp_recv(_pcb, &_tcp_recv); -// tcp_sent(_pcb, &_tcp_sent); -// tcp_poll(_pcb, &_tcp_poll, 1); - } - if(_connect_cb) { - _connect_cb(_connect_cb_arg, this); - } - return ERR_OK; +int8_t AsyncClient::_connected(tcp_pcb* pcb, int8_t err) { + _pcb = reinterpret_cast(pcb); + if (_pcb) { + _rx_last_packet = millis(); + } + if (_connect_cb) { + _connect_cb(_connect_cb_arg, this); + } + return ERR_OK; } void AsyncClient::_error(int8_t err) { - if(_pcb){ - tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - _pcb = NULL; - } - if(_error_cb) { - _error_cb(_error_cb_arg, this, err); - } - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } -} - -//In LwIP Thread -int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { - if(!_pcb || pcb != _pcb){ - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } + if (_pcb) { + TCP_MUTEX_LOCK(); tcp_arg(_pcb, NULL); - if(_pcb->state == LISTEN) { - tcp_sent(_pcb, NULL); - tcp_recv(_pcb, NULL); - tcp_err(_pcb, NULL); - tcp_poll(_pcb, NULL, 0); - } - if(tcp_close(_pcb) != ERR_OK) { - tcp_abort(_pcb); + if (_pcb->state == LISTEN) { + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); } + TCP_MUTEX_UNLOCK(); _free_closed_slot(); _pcb = NULL; - return ERR_OK; + } + if (_error_cb) { + _error_cb(_error_cb_arg, this, err); + } + if (_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } } -//In Async Thread -int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { - _tcp_clear_events(this); - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } +// In LwIP Thread +int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { + if (!_pcb || pcb != _pcb) { + log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); return ERR_OK; + } + tcp_arg(_pcb, NULL); + if (_pcb->state == LISTEN) { + tcp_sent(_pcb, NULL); + tcp_recv(_pcb, NULL); + tcp_err(_pcb, NULL); + tcp_poll(_pcb, NULL, 0); + } + if (tcp_close(_pcb) != ERR_OK) { + tcp_abort(_pcb); + } + _free_closed_slot(); + _pcb = NULL; + return ERR_OK; +} + +// In Async Thread +int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { + _tcp_clear_events(this); + if (_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + return ERR_OK; } int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { - _rx_last_packet = millis(); - //log_i("%u", len); - _pcb_busy = false; - if(_sent_cb) { - _sent_cb(_sent_cb_arg, this, len, (millis() - _pcb_sent_at)); - } - return ERR_OK; + _rx_last_ack = _rx_last_packet = millis(); + if (_sent_cb) { + _sent_cb(_sent_cb_arg, this, len, (_rx_last_packet - _tx_last_packet)); + } + return ERR_OK; } int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { - while(pb != NULL) { - _rx_last_packet = millis(); - //we should not ack before we assimilate the data - _ack_pcb = true; - pbuf *b = pb; - pb = b->next; - b->next = NULL; - if(_pb_cb){ - _pb_cb(_pb_cb_arg, this, b); - } else { - if(_recv_cb) { - _recv_cb(_recv_cb_arg, this, b->payload, b->len); - } - if(!_ack_pcb) { - _rx_ack_len += b->len; - } else if(_pcb) { - _tcp_recved(_pcb, _closed_slot, b->len); - } - pbuf_free(b); - } - } - return ERR_OK; -} - -int8_t AsyncClient::_poll(tcp_pcb* pcb){ - if(!_pcb){ - log_w("pcb is NULL"); - return ERR_OK; - } - if(pcb != _pcb){ - log_e("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); - return ERR_OK; - } - - uint32_t now = millis(); - - // ACK Timeout - if(_pcb_busy && _ack_timeout && (now - _pcb_sent_at) >= _ack_timeout){ - _pcb_busy = false; - log_w("ack timeout %d", pcb->state); - if(_timeout_cb) - _timeout_cb(_timeout_cb_arg, this, (now - _pcb_sent_at)); - return ERR_OK; - } - // RX Timeout - if(_rx_since_timeout && (now - _rx_last_packet) >= (_rx_since_timeout * 1000)){ - log_w("rx timeout %d", pcb->state); - _close(); - return ERR_OK; - } - // Everything is fine - if(_poll_cb) { - _poll_cb(_poll_cb_arg, this); - } - return ERR_OK; -} - -void AsyncClient::_dns_found(struct ip_addr *ipaddr){ - if(ipaddr && ipaddr->u_addr.ip4.addr){ - connect(IPAddress(ipaddr->u_addr.ip4.addr), _connect_port); + while (pb != NULL) { + _rx_last_packet = millis(); + // we should not ack before we assimilate the data + _ack_pcb = true; + pbuf* b = pb; + pb = b->next; + b->next = NULL; + if (_pb_cb) { + _pb_cb(_pb_cb_arg, this, b); } else { - if(_error_cb) { - _error_cb(_error_cb_arg, this, -55); - } - if(_discard_cb) { - _discard_cb(_discard_cb_arg, this); - } + if (_recv_cb) { + _recv_cb(_recv_cb_arg, this, b->payload, b->len); + } + if (!_ack_pcb) { + _rx_ack_len += b->len; + } else if (_pcb) { + _tcp_recved(_pcb, _closed_slot, b->len); + } } + pbuf_free(b); + } + return ERR_OK; +} + +int8_t AsyncClient::_poll(tcp_pcb* pcb) { + if (!_pcb) { + // log_d("pcb is NULL"); + return ERR_OK; + } + if (pcb != _pcb) { + log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb); + return ERR_OK; + } + + uint32_t now = millis(); + + // ACK Timeout + if (_ack_timeout) { + const uint32_t one_day = 86400000; + bool last_tx_is_after_last_ack = (_rx_last_ack - _tx_last_packet + one_day) < one_day; + if (last_tx_is_after_last_ack && (now - _tx_last_packet) >= _ack_timeout) { + log_d("ack timeout %d", pcb->state); + if (_timeout_cb) + _timeout_cb(_timeout_cb_arg, this, (now - _tx_last_packet)); + return ERR_OK; + } + } + // RX Timeout + if (_rx_timeout && (now - _rx_last_packet) >= (_rx_timeout * 1000)) { + log_d("rx timeout %d", pcb->state); + _close(); + return ERR_OK; + } + // Everything is fine + if (_poll_cb) { + _poll_cb(_poll_cb_arg, this); + } + return ERR_OK; +} + +void AsyncClient::_dns_found(struct ip_addr* ipaddr) { +#if ESP_IDF_VERSION_MAJOR < 5 + if (ipaddr && IP_IS_V4(ipaddr)) { + connect(IPAddress(ip_addr_get_ip4_u32(ipaddr)), _connect_port); + #if LWIP_IPV6 + } else if (ipaddr && ipaddr->u_addr.ip6.addr) { + connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port); + #endif +#else + if (ipaddr) { + IPAddress ip; + ip.from_ip_addr_t(ipaddr); + connect(ip, _connect_port); +#endif + } else { + if (_error_cb) { + _error_cb(_error_cb_arg, this, -55); + } + if (_discard_cb) { + _discard_cb(_discard_cb_arg, this); + } + } } /* * Public Helper Methods * */ -void AsyncClient::stop() { - close(false); -} - -bool AsyncClient::free(){ - if(!_pcb) { - return true; - } - if(_pcb->state == 0 || _pcb->state > 4) { - return true; - } - return false; -} - -size_t AsyncClient::write(const char* data) { - if(data == NULL) { - return 0; - } - return write(data, strlen(data)); +bool AsyncClient::free() { + if (!_pcb) { + return true; + } + if (_pcb->state == CLOSED || _pcb->state > ESTABLISHED) { + return true; + } + return false; } size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { - size_t will_send = add(data, size, apiflags); - if(!will_send || !send()) { - return 0; - } - return will_send; + size_t will_send = add(data, size, apiflags); + if (!will_send || !send()) { + return 0; + } + return will_send; } -void AsyncClient::setRxTimeout(uint32_t timeout){ - _rx_since_timeout = timeout; +void AsyncClient::setRxTimeout(uint32_t timeout) { + _rx_timeout = timeout; } -uint32_t AsyncClient::getRxTimeout(){ - return _rx_since_timeout; +uint32_t AsyncClient::getRxTimeout() { + return _rx_timeout; } -uint32_t AsyncClient::getAckTimeout(){ - return _ack_timeout; +uint32_t AsyncClient::getAckTimeout() { + return _ack_timeout; } -void AsyncClient::setAckTimeout(uint32_t timeout){ - _ack_timeout = timeout; +void AsyncClient::setAckTimeout(uint32_t timeout) { + _ack_timeout = timeout; } -void AsyncClient::setNoDelay(bool nodelay){ - if(!_pcb) { - return; - } - if(nodelay) { - tcp_nagle_disable(_pcb); - } else { - tcp_nagle_enable(_pcb); - } +void AsyncClient::setNoDelay(bool nodelay) { + if (!_pcb) { + return; + } + if (nodelay) { + tcp_nagle_disable(_pcb); + } else { + tcp_nagle_enable(_pcb); + } } -bool AsyncClient::getNoDelay(){ - if(!_pcb) { - return false; - } - return tcp_nagle_disabled(_pcb); +bool AsyncClient::getNoDelay() { + if (!_pcb) { + return false; + } + return tcp_nagle_disabled(_pcb); } -uint16_t AsyncClient::getMss(){ - if(!_pcb) { - return 0; - } - return tcp_mss(_pcb); +void AsyncClient::setKeepAlive(uint32_t ms, uint8_t cnt) { + if (ms != 0) { + _pcb->so_options |= SOF_KEEPALIVE; // Turn on TCP Keepalive for the given pcb + // Set the time between keepalive messages in milli-seconds + _pcb->keep_idle = ms; + _pcb->keep_intvl = ms; + _pcb->keep_cnt = cnt; // The number of unanswered probes required to force closure of the socket + } else { + _pcb->so_options &= ~SOF_KEEPALIVE; // Turn off TCP Keepalive for the given pcb + } +} + +uint16_t AsyncClient::getMss() { + if (!_pcb) { + return 0; + } + return tcp_mss(_pcb); } uint32_t AsyncClient::getRemoteAddress() { - if(!_pcb) { - return 0; - } - return _pcb->remote_ip.u_addr.ip4.addr; + if (!_pcb) { + return 0; + } +#if LWIP_IPV4 && LWIP_IPV6 + return _pcb->remote_ip.u_addr.ip4.addr; +#else + return _pcb->remote_ip.addr; +#endif } +#if LWIP_IPV6 +ip6_addr_t AsyncClient::getRemoteAddress6() { + if (!_pcb) { + ip6_addr_t nulladdr; + ip6_addr_set_zero(&nulladdr); + return nulladdr; + } + return _pcb->remote_ip.u_addr.ip6; +} + +ip6_addr_t AsyncClient::getLocalAddress6() { + if (!_pcb) { + ip6_addr_t nulladdr; + ip6_addr_set_zero(&nulladdr); + return nulladdr; + } + return _pcb->local_ip.u_addr.ip6; +} + #if ESP_IDF_VERSION_MAJOR < 5 +IPv6Address AsyncClient::remoteIP6() { + return IPv6Address(getRemoteAddress6().addr); +} + +IPv6Address AsyncClient::localIP6() { + return IPv6Address(getLocalAddress6().addr); +} + #else +IPAddress AsyncClient::remoteIP6() { + if (!_pcb) { + return IPAddress(IPType::IPv6); + } + IPAddress ip; + ip.from_ip_addr_t(&(_pcb->remote_ip)); + return ip; +} + +IPAddress AsyncClient::localIP6() { + if (!_pcb) { + return IPAddress(IPType::IPv6); + } + IPAddress ip; + ip.from_ip_addr_t(&(_pcb->local_ip)); + return ip; +} + #endif +#endif + uint16_t AsyncClient::getRemotePort() { - if(!_pcb) { - return 0; - } - return _pcb->remote_port; + if (!_pcb) { + return 0; + } + return _pcb->remote_port; } uint32_t AsyncClient::getLocalAddress() { - if(!_pcb) { - return 0; - } - return _pcb->local_ip.u_addr.ip4.addr; + if (!_pcb) { + return 0; + } +#if LWIP_IPV4 && LWIP_IPV6 + return _pcb->local_ip.u_addr.ip4.addr; +#else + return _pcb->local_ip.addr; +#endif } uint16_t AsyncClient::getLocalPort() { - if(!_pcb) { - return 0; - } - return _pcb->local_port; + if (!_pcb) { + return 0; + } + return _pcb->local_port; } IPAddress AsyncClient::remoteIP() { - return IPAddress(getRemoteAddress()); +#if ESP_IDF_VERSION_MAJOR < 5 + return IPAddress(getRemoteAddress()); +#else + if (!_pcb) { + return IPAddress(); + } + IPAddress ip; + ip.from_ip_addr_t(&(_pcb->remote_ip)); + return ip; +#endif } uint16_t AsyncClient::remotePort() { - return getRemotePort(); + return getRemotePort(); } IPAddress AsyncClient::localIP() { - return IPAddress(getLocalAddress()); +#if ESP_IDF_VERSION_MAJOR < 5 + return IPAddress(getLocalAddress()); +#else + if (!_pcb) { + return IPAddress(); + } + IPAddress ip; + ip.from_ip_addr_t(&(_pcb->local_ip)); + return ip; +#endif } uint16_t AsyncClient::localPort() { - return getLocalPort(); + return getLocalPort(); } uint8_t AsyncClient::state() { - if(!_pcb) { - return 0; - } - return _pcb->state; + if (!_pcb) { + return 0; + } + return _pcb->state; } -bool AsyncClient::connected(){ - if (!_pcb) { - return false; - } - return _pcb->state == 4; +bool AsyncClient::connected() { + if (!_pcb) { + return false; + } + return _pcb->state == ESTABLISHED; } -bool AsyncClient::connecting(){ - if (!_pcb) { - return false; - } - return _pcb->state > 0 && _pcb->state < 4; +bool AsyncClient::connecting() { + if (!_pcb) { + return false; + } + return _pcb->state > CLOSED && _pcb->state < ESTABLISHED; } -bool AsyncClient::disconnecting(){ - if (!_pcb) { - return false; - } - return _pcb->state > 4 && _pcb->state < 10; +bool AsyncClient::disconnecting() { + if (!_pcb) { + return false; + } + return _pcb->state > ESTABLISHED && _pcb->state < TIME_WAIT; } -bool AsyncClient::disconnected(){ - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state == 10; +bool AsyncClient::disconnected() { + if (!_pcb) { + return true; + } + return _pcb->state == CLOSED || _pcb->state == TIME_WAIT; } -bool AsyncClient::freeable(){ - if (!_pcb) { - return true; - } - return _pcb->state == 0 || _pcb->state > 4; +bool AsyncClient::freeable() { + if (!_pcb) { + return true; + } + return _pcb->state == CLOSED || _pcb->state > ESTABLISHED; } -bool AsyncClient::canSend(){ - return space() > 0; +bool AsyncClient::canSend() { + return space() > 0; } -const char * AsyncClient::errorToString(int8_t error){ - switch(error){ - case ERR_OK: return "OK"; - case ERR_MEM: return "Out of memory error"; - case ERR_BUF: return "Buffer error"; - case ERR_TIMEOUT: return "Timeout"; - case ERR_RTE: return "Routing problem"; - case ERR_INPROGRESS: return "Operation in progress"; - case ERR_VAL: return "Illegal value"; - case ERR_WOULDBLOCK: return "Operation would block"; - case ERR_USE: return "Address in use"; - case ERR_ALREADY: return "Already connected"; - case ERR_CONN: return "Not connected"; - case ERR_IF: return "Low-level netif error"; - case ERR_ABRT: return "Connection aborted"; - case ERR_RST: return "Connection reset"; - case ERR_CLSD: return "Connection closed"; - case ERR_ARG: return "Illegal argument"; - case -55: return "DNS failed"; - default: return "UNKNOWN"; - } +const char* AsyncClient::errorToString(int8_t error) { + switch (error) { + case ERR_OK: + return "OK"; + case ERR_MEM: + return "Out of memory error"; + case ERR_BUF: + return "Buffer error"; + case ERR_TIMEOUT: + return "Timeout"; + case ERR_RTE: + return "Routing problem"; + case ERR_INPROGRESS: + return "Operation in progress"; + case ERR_VAL: + return "Illegal value"; + case ERR_WOULDBLOCK: + return "Operation would block"; + case ERR_USE: + return "Address in use"; + case ERR_ALREADY: + return "Already connected"; + case ERR_CONN: + return "Not connected"; + case ERR_IF: + return "Low-level netif error"; + case ERR_ABRT: + return "Connection aborted"; + case ERR_RST: + return "Connection reset"; + case ERR_CLSD: + return "Connection closed"; + case ERR_ARG: + return "Illegal argument"; + case -55: + return "DNS failed"; + default: + return "UNKNOWN"; + } } -const char * AsyncClient::stateToString(){ - switch(state()){ - case 0: return "Closed"; - case 1: return "Listen"; - case 2: return "SYN Sent"; - case 3: return "SYN Received"; - case 4: return "Established"; - case 5: return "FIN Wait 1"; - case 6: return "FIN Wait 2"; - case 7: return "Close Wait"; - case 8: return "Closing"; - case 9: return "Last ACK"; - case 10: return "Time Wait"; - default: return "UNKNOWN"; - } +const char* AsyncClient::stateToString() { + switch (state()) { + case 0: + return "Closed"; + case 1: + return "Listen"; + case 2: + return "SYN Sent"; + case 3: + return "SYN Received"; + case 4: + return "Established"; + case 5: + return "FIN Wait 1"; + case 6: + return "FIN Wait 2"; + case 7: + return "Close Wait"; + case 8: + return "Closing"; + case 9: + return "Last ACK"; + case 10: + return "Time Wait"; + default: + return "UNKNOWN"; + } } /* * Static Callbacks (LwIP C2C++ interconnect) * */ -void AsyncClient::_s_dns_found(const char * name, struct ip_addr * ipaddr, void * arg){ - reinterpret_cast(arg)->_dns_found(ipaddr); +void AsyncClient::_s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) { + reinterpret_cast(arg)->_dns_found(ipaddr); } -int8_t AsyncClient::_s_poll(void * arg, struct tcp_pcb * pcb) { - return reinterpret_cast(arg)->_poll(pcb); +int8_t AsyncClient::_s_poll(void* arg, struct tcp_pcb* pcb) { + return reinterpret_cast(arg)->_poll(pcb); } -int8_t AsyncClient::_s_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *pb, int8_t err) { - return reinterpret_cast(arg)->_recv(pcb, pb, err); +int8_t AsyncClient::_s_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) { + return reinterpret_cast(arg)->_recv(pcb, pb, err); } -int8_t AsyncClient::_s_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_fin(pcb, err); +int8_t AsyncClient::_s_fin(void* arg, struct tcp_pcb* pcb, int8_t err) { + return reinterpret_cast(arg)->_fin(pcb, err); } -int8_t AsyncClient::_s_lwip_fin(void * arg, struct tcp_pcb * pcb, int8_t err) { - return reinterpret_cast(arg)->_lwip_fin(pcb, err); +int8_t AsyncClient::_s_lwip_fin(void* arg, struct tcp_pcb* pcb, int8_t err) { + return reinterpret_cast(arg)->_lwip_fin(pcb, err); } -int8_t AsyncClient::_s_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) { - return reinterpret_cast(arg)->_sent(pcb, len); +int8_t AsyncClient::_s_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) { + return reinterpret_cast(arg)->_sent(pcb, len); } -void AsyncClient::_s_error(void * arg, int8_t err) { - reinterpret_cast(arg)->_error(err); +void AsyncClient::_s_error(void* arg, int8_t err) { + reinterpret_cast(arg)->_error(err); } -int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ - return reinterpret_cast(arg)->_connected(pcb, err); +int8_t AsyncClient::_s_connected(void* arg, struct tcp_pcb* pcb, int8_t err) { + return reinterpret_cast(arg)->_connected(pcb, err); } /* @@ -1257,129 +1512,150 @@ int8_t AsyncClient::_s_connected(void * arg, void * pcb, int8_t err){ */ AsyncServer::AsyncServer(IPAddress addr, uint16_t port) -: _port(port) -, _addr(addr) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} + : _port(port) +#if ESP_IDF_VERSION_MAJOR < 5 + , + _bind4(true), _bind6(false) +#else + , + _bind4(addr.type() != IPType::IPv6), _bind6(addr.type() == IPType::IPv6) +#endif + , + _addr(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { +} + +#if ESP_IDF_VERSION_MAJOR < 5 +AsyncServer::AsyncServer(IPv6Address addr, uint16_t port) + : _port(port), _bind4(false), _bind6(true), _addr6(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) {} +#endif AsyncServer::AsyncServer(uint16_t port) -: _port(port) -, _addr((uint32_t) IPADDR_ANY) -, _noDelay(false) -, _pcb(0) -, _connect_cb(0) -, _connect_cb_arg(0) -{} - -AsyncServer::~AsyncServer(){ - end(); + : _port(port), _bind4(true), _bind6(false), _addr((uint32_t)IPADDR_ANY) +#if ESP_IDF_VERSION_MAJOR < 5 + , + _addr6() +#endif + , + _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { } -void AsyncServer::onClient(AcConnectHandler cb, void* arg){ - _connect_cb = cb; - _connect_cb_arg = arg; +AsyncServer::~AsyncServer() { + end(); } -void AsyncServer::begin(){ - if(_pcb) { - return; - } +void AsyncServer::onClient(AcConnectHandler cb, void* arg) { + _connect_cb = cb; + _connect_cb_arg = arg; +} - if(!_start_async_task()){ - log_e("failed to start task"); - return; - } - int8_t err; - TCP_MUTEX_LOCK(); - _pcb = tcp_new_ip_type(IPADDR_TYPE_V4); - TCP_MUTEX_UNLOCK(); - if (!_pcb){ - log_e("_pcb == NULL"); - return; - } +void AsyncServer::begin() { + if (_pcb) { + return; + } - ip_addr_t local_addr; + if (!_start_async_task()) { + log_e("failed to start task"); + return; + } + int8_t err; + TCP_MUTEX_LOCK(); + _pcb = tcp_new_ip_type(_bind4 && _bind6 ? IPADDR_TYPE_ANY : (_bind6 ? IPADDR_TYPE_V6 : IPADDR_TYPE_V4)); + TCP_MUTEX_UNLOCK(); + if (!_pcb) { + log_e("_pcb == NULL"); + return; + } + + ip_addr_t local_addr; +#if ESP_IDF_VERSION_MAJOR < 5 + if (_bind6) { // _bind6 && _bind4 both at the same time is not supported on Arduino 2 in this lib API + local_addr.type = IPADDR_TYPE_V6; + memcpy(local_addr.u_addr.ip6.addr, static_cast(_addr6), sizeof(uint32_t) * 4); + } else { local_addr.type = IPADDR_TYPE_V4; - local_addr.u_addr.ip4.addr = (uint32_t) _addr; - err = _tcp_bind(_pcb, &local_addr, _port); + local_addr.u_addr.ip4.addr = _addr; + } +#else + _addr.to_ip_addr_t(&local_addr); +#endif + err = _tcp_bind(_pcb, &local_addr, _port); - if (err != ERR_OK) { - _tcp_close(_pcb, -1); - log_e("bind error: %d", err); - return; - } + if (err != ERR_OK) { + _tcp_close(_pcb, -1); + log_e("bind error: %d", err); + return; + } - static uint8_t backlog = 5; - _pcb = _tcp_listen_with_backlog(_pcb, backlog); - if (!_pcb) { - log_e("listen_pcb == NULL"); - return; - } + static uint8_t backlog = 5; + _pcb = _tcp_listen_with_backlog(_pcb, backlog); + if (!_pcb) { + log_e("listen_pcb == NULL"); + return; + } + TCP_MUTEX_LOCK(); + tcp_arg(_pcb, (void*)this); + tcp_accept(_pcb, &_s_accept); + TCP_MUTEX_UNLOCK(); +} + +void AsyncServer::end() { + if (_pcb) { TCP_MUTEX_LOCK(); - tcp_arg(_pcb, (void*) this); - tcp_accept(_pcb, &_s_accept); - TCP_MUTEX_UNLOCK(); -} - -void AsyncServer::end(){ - if(_pcb){ - TCP_MUTEX_LOCK(); - tcp_arg(_pcb, NULL); - tcp_accept(_pcb, NULL); - TCP_MUTEX_UNLOCK(); - if(tcp_close(_pcb) != ERR_OK){ - _tcp_abort(_pcb, -1); - } - _pcb = NULL; + tcp_arg(_pcb, NULL); + tcp_accept(_pcb, NULL); + if (tcp_close(_pcb) != ERR_OK) { + TCP_MUTEX_UNLOCK(); + _tcp_abort(_pcb, -1); + } else { + TCP_MUTEX_UNLOCK(); } + _pcb = NULL; + } } -//runs on LwIP thread -int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err){ - //ets_printf("+A: 0x%08x\n", pcb); - if(_connect_cb){ - AsyncClient *c = new AsyncClient(pcb); - if(c){ - c->setNoDelay(_noDelay); - return _tcp_accept(this, c); - } +// runs on LwIP thread +int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err) { + // ets_printf("+A: 0x%08x\n", pcb); + if (_connect_cb) { + AsyncClient* c = new AsyncClient(pcb); + if (c) { + c->setNoDelay(_noDelay); + return _tcp_accept(this, c); } - if(tcp_close(pcb) != ERR_OK){ - tcp_abort(pcb); - } - log_e("FAIL"); - return ERR_OK; + } + if (tcp_close(pcb) != ERR_OK) { + tcp_abort(pcb); + } + log_d("FAIL"); + return ERR_OK; } -int8_t AsyncServer::_accepted(AsyncClient* client){ - if(_connect_cb){ - _connect_cb(_connect_cb_arg, client); - } - return ERR_OK; +int8_t AsyncServer::_accepted(AsyncClient* client) { + if (_connect_cb) { + _connect_cb(_connect_cb_arg, client); + } + return ERR_OK; } -void AsyncServer::setNoDelay(bool nodelay){ - _noDelay = nodelay; +void AsyncServer::setNoDelay(bool nodelay) { + _noDelay = nodelay; } -bool AsyncServer::getNoDelay(){ - return _noDelay; +bool AsyncServer::getNoDelay() { + return _noDelay; } -uint8_t AsyncServer::status(){ - if (!_pcb) { - return 0; - } - return _pcb->state; +uint8_t AsyncServer::status() { + if (!_pcb) { + return 0; + } + return _pcb->state; } -int8_t AsyncServer::_s_accept(void * arg, tcp_pcb * pcb, int8_t err){ - return reinterpret_cast(arg)->_accept(pcb, err); +int8_t AsyncServer::_s_accept(void* arg, tcp_pcb* pcb, int8_t err) { + return reinterpret_cast(arg)->_accept(pcb, err); } -int8_t AsyncServer::_s_accepted(void *arg, AsyncClient* client){ - return reinterpret_cast(arg)->_accepted(client); +int8_t AsyncServer::_s_accepted(void* arg, AsyncClient* client) { + return reinterpret_cast(arg)->_accepted(client); } diff --git a/src/AsyncTCP.h b/src/AsyncTCP.h index ac87ded..431785f 100644 --- a/src/AsyncTCP.h +++ b/src/AsyncTCP.h @@ -22,31 +22,70 @@ #ifndef ASYNCTCP_H_ #define ASYNCTCP_H_ -#include "IPAddress.h" -#include "sdkconfig.h" -#include -extern "C" { - #include "freertos/semphr.h" - #include "lwip/pbuf.h" -} +#define ASYNCTCP_VERSION "3.3.2" +#define ASYNCTCP_VERSION_MAJOR 3 +#define ASYNCTCP_VERSION_MINOR 3 +#define ASYNCTCP_VERSION_REVISION 2 +#define ASYNCTCP_FORK_ESP32Async -//If core is not defined, then we are running in Arduino or PIO +#include "IPAddress.h" +#if ESP_IDF_VERSION_MAJOR < 5 + #include "IPv6Address.h" +#endif +#include "lwip/ip6_addr.h" +#include "lwip/ip_addr.h" +#include + +#ifndef LIBRETINY + #include "sdkconfig.h" +extern "C" { + #include "freertos/semphr.h" + #include "lwip/pbuf.h" +} +#else +extern "C" { + #include + #include +} + #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core +#endif + +// If core is not defined, then we are running in Arduino or PIO #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE -#define CONFIG_ASYNC_TCP_RUNNING_CORE -1 //any available core -#define CONFIG_ASYNC_TCP_USE_WDT 1 //if enabled, adds between 33us and 200us per event + #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core +#endif + +// guard AsyncTCP task with watchdog +#ifndef CONFIG_ASYNC_TCP_USE_WDT + #define CONFIG_ASYNC_TCP_USE_WDT 1 +#endif + +#ifndef CONFIG_ASYNC_TCP_STACK_SIZE + #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 +#endif + +#ifndef CONFIG_ASYNC_TCP_PRIORITY + #define CONFIG_ASYNC_TCP_PRIORITY 10 +#endif + +#ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE + #define CONFIG_ASYNC_TCP_QUEUE_SIZE 64 +#endif + +#ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME + #define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 #endif class AsyncClient; -#define ASYNC_MAX_ACK_TIME 5000 -#define ASYNC_WRITE_FLAG_COPY 0x01 //will allocate new buffer to hold the data while sending (else will hold reference to the data given) -#define ASYNC_WRITE_FLAG_MORE 0x02 //will not send PSH flag, meaning that there should be more data to be sent before the application should react. +#define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given) +#define ASYNC_WRITE_FLAG_MORE 0x02 // will not send PSH flag, meaning that there should be more data to be sent before the application should react. typedef std::function AcConnectHandler; typedef std::function AcAckHandler; typedef std::function AcErrorHandler; -typedef std::function AcDataHandler; -typedef std::function AcPacketHandler; +typedef std::function AcDataHandler; +typedef std::function AcPacketHandler; typedef std::function AcTimeoutHandler; struct tcp_pcb; @@ -57,91 +96,172 @@ class AsyncClient { AsyncClient(tcp_pcb* pcb = 0); ~AsyncClient(); - AsyncClient & operator=(const AsyncClient &other); - AsyncClient & operator+=(const AsyncClient &other); + AsyncClient& operator=(const AsyncClient& other); + AsyncClient& operator+=(const AsyncClient& other); - bool operator==(const AsyncClient &other); + bool operator==(const AsyncClient& other); - bool operator!=(const AsyncClient &other) { + bool operator!=(const AsyncClient& other) { return !(*this == other); } - bool connect(IPAddress ip, uint16_t port); + bool connect(const IPAddress& ip, uint16_t port); +#if ESP_IDF_VERSION_MAJOR < 5 + bool connect(const IPv6Address& ip, uint16_t port); +#endif bool connect(const char* host, uint16_t port); + /** + * @brief close connection + * + * @param now - ignored + */ void close(bool now = false); - void stop(); + // same as close() + void stop() { close(false); }; int8_t abort(); bool free(); - bool canSend();//ack is not pending - size_t space();//space available in the TCP window - size_t add(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY);//add for sending - bool send();//send all data added with the method above + // ack is not pending + bool canSend(); + // TCP buffer space available + size_t space(); - //write equals add()+send() - size_t write(const char* data); - size_t write(const char* data, size_t size, uint8_t apiflags=ASYNC_WRITE_FLAG_COPY); //only when canSend() == true + /** + * @brief add data to be send (but do not send yet) + * @note add() would call lwip's tcp_write() + By default apiflags=ASYNC_WRITE_FLAG_COPY + You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer, + but looks like it does not work for Arduino's lwip in ESP32/IDF at least + it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30 + if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF + https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744 + * + * @param data + * @param size + * @param apiflags + * @return size_t amount of data that has been copied + */ + size_t add(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); + + /** + * @brief send data previously add()'ed + * + * @return true on success + * @return false on error + */ + bool send(); + + /** + * @brief add and enqueue data for sending + * @note it is same as add() + send() + * @note only make sense when canSend() == true + * + * @param data + * @param size + * @param apiflags + * @return size_t + */ + size_t write(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); + + /** + * @brief add and enque data for sending + * @note treats data as null-terminated string + * + * @param data + * @return size_t + */ + size_t write(const char* data) { return data == NULL ? 0 : write(data, strlen(data)); }; uint8_t state(); bool connecting(); bool connected(); bool disconnecting(); bool disconnected(); - bool freeable();//disconnected or disconnecting + + // disconnected or disconnecting + bool freeable(); uint16_t getMss(); uint32_t getRxTimeout(); - void setRxTimeout(uint32_t timeout);//no RX data timeout for the connection in seconds + // no RX data timeout for the connection in seconds + void setRxTimeout(uint32_t timeout); uint32_t getAckTimeout(); - void setAckTimeout(uint32_t timeout);//no ACK timeout for the last sent packet in milliseconds + // no ACK timeout for the last sent packet in milliseconds + void setAckTimeout(uint32_t timeout); void setNoDelay(bool nodelay); bool getNoDelay(); + void setKeepAlive(uint32_t ms, uint8_t cnt); + uint32_t getRemoteAddress(); uint16_t getRemotePort(); uint32_t getLocalAddress(); uint16_t getLocalPort(); +#if LWIP_IPV6 + ip6_addr_t getRemoteAddress6(); + ip6_addr_t getLocalAddress6(); + #if ESP_IDF_VERSION_MAJOR < 5 + IPv6Address remoteIP6(); + IPv6Address localIP6(); + #else + IPAddress remoteIP6(); + IPAddress localIP6(); + #endif +#endif - //compatibility + // compatibility IPAddress remoteIP(); - uint16_t remotePort(); + uint16_t remotePort(); IPAddress localIP(); - uint16_t localPort(); + uint16_t localPort(); - void onConnect(AcConnectHandler cb, void* arg = 0); //on successful connect - void onDisconnect(AcConnectHandler cb, void* arg = 0); //disconnected - void onAck(AcAckHandler cb, void* arg = 0); //ack received - void onError(AcErrorHandler cb, void* arg = 0); //unsuccessful connect or error - void onData(AcDataHandler cb, void* arg = 0); //data received (called if onPacket is not used) - void onPacket(AcPacketHandler cb, void* arg = 0); //data received - void onTimeout(AcTimeoutHandler cb, void* arg = 0); //ack timeout - void onPoll(AcConnectHandler cb, void* arg = 0); //every 125ms when connected + // set callback - on successful connect + void onConnect(AcConnectHandler cb, void* arg = 0); + // set callback - disconnected + void onDisconnect(AcConnectHandler cb, void* arg = 0); + // set callback - ack received + void onAck(AcAckHandler cb, void* arg = 0); + // set callback - unsuccessful connect or error + void onError(AcErrorHandler cb, void* arg = 0); + // set callback - data received (called if onPacket is not used) + void onData(AcDataHandler cb, void* arg = 0); + // set callback - data received + void onPacket(AcPacketHandler cb, void* arg = 0); + // set callback - ack timeout + void onTimeout(AcTimeoutHandler cb, void* arg = 0); + // set callback - every 125ms when connected + void onPoll(AcConnectHandler cb, void* arg = 0); - void ackPacket(struct pbuf * pb);//ack pbuf from onPacket - size_t ack(size_t len); //ack data that you have not acked using the method below - void ackLater(){ _ack_pcb = false; } //will not ack the current packet. Call from onData + // ack pbuf from onPacket + void ackPacket(struct pbuf* pb); + // ack data that you have not acked using the method below + size_t ack(size_t len); + // will not ack the current packet. Call from onData + void ackLater() { _ack_pcb = false; } - const char * errorToString(int8_t error); - const char * stateToString(); + static const char* errorToString(int8_t error); + const char* stateToString(); - //Do not use any of the functions below! - static int8_t _s_poll(void *arg, struct tcp_pcb *tpcb); - static int8_t _s_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *pb, int8_t err); - static int8_t _s_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static int8_t _s_lwip_fin(void *arg, struct tcp_pcb *tpcb, int8_t err); - static void _s_error(void *arg, int8_t err); - static int8_t _s_sent(void *arg, struct tcp_pcb *tpcb, uint16_t len); - static int8_t _s_connected(void* arg, void* tpcb, int8_t err); - static void _s_dns_found(const char *name, struct ip_addr *ipaddr, void *arg); + // internal callbacks - Do NOT call any of the functions below in user code! + static int8_t _s_poll(void* arg, struct tcp_pcb* tpcb); + static int8_t _s_recv(void* arg, struct tcp_pcb* tpcb, struct pbuf* pb, int8_t err); + static int8_t _s_fin(void* arg, struct tcp_pcb* tpcb, int8_t err); + static int8_t _s_lwip_fin(void* arg, struct tcp_pcb* tpcb, int8_t err); + static void _s_error(void* arg, int8_t err); + static int8_t _s_sent(void* arg, struct tcp_pcb* tpcb, uint16_t len); + static int8_t _s_connected(void* arg, struct tcp_pcb* tpcb, int8_t err); + static void _s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg); int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err); - tcp_pcb * pcb(){ return _pcb; } + tcp_pcb* pcb() { return _pcb; } protected: + bool _connect(ip_addr_t addr, uint16_t port); + tcp_pcb* _pcb; - int8_t _closed_slot; + int8_t _closed_slot; AcConnectHandler _connect_cb; void* _connect_cb_arg; @@ -160,25 +280,25 @@ class AsyncClient { AcConnectHandler _poll_cb; void* _poll_cb_arg; - bool _pcb_busy; - uint32_t _pcb_sent_at; bool _ack_pcb; + uint32_t _tx_last_packet; uint32_t _rx_ack_len; uint32_t _rx_last_packet; - uint32_t _rx_since_timeout; + uint32_t _rx_timeout; + uint32_t _rx_last_ack; uint32_t _ack_timeout; uint16_t _connect_port; int8_t _close(); void _free_closed_slot(); - void _allocate_closed_slot(); - int8_t _connected(void* pcb, int8_t err); + bool _allocate_closed_slot(); + int8_t _connected(tcp_pcb* pcb, int8_t err); void _error(int8_t err); int8_t _poll(tcp_pcb* pcb); int8_t _sent(tcp_pcb* pcb, uint16_t len); int8_t _fin(tcp_pcb* pcb, int8_t err); int8_t _lwip_fin(tcp_pcb* pcb, int8_t err); - void _dns_found(struct ip_addr *ipaddr); + void _dns_found(struct ip_addr* ipaddr); public: AsyncClient* prev; @@ -188,6 +308,9 @@ class AsyncClient { class AsyncServer { public: AsyncServer(IPAddress addr, uint16_t port); +#if ESP_IDF_VERSION_MAJOR < 5 + AsyncServer(IPv6Address addr, uint16_t port); +#endif AsyncServer(uint16_t port); ~AsyncServer(); void onClient(AcConnectHandler cb, void* arg); @@ -197,13 +320,18 @@ class AsyncServer { bool getNoDelay(); uint8_t status(); - //Do not use any of the functions below! - static int8_t _s_accept(void *arg, tcp_pcb* newpcb, int8_t err); - static int8_t _s_accepted(void *arg, AsyncClient* client); + // Do not use any of the functions below! + static int8_t _s_accept(void* arg, tcp_pcb* newpcb, int8_t err); + static int8_t _s_accepted(void* arg, AsyncClient* client); protected: uint16_t _port; + bool _bind4 = false; + bool _bind6 = false; IPAddress _addr; +#if ESP_IDF_VERSION_MAJOR < 5 + IPv6Address _addr6; +#endif bool _noDelay; tcp_pcb* _pcb; AcConnectHandler _connect_cb; @@ -213,5 +341,4 @@ class AsyncServer { int8_t _accepted(AsyncClient* client); }; - #endif /* ASYNCTCP_H_ */