diff --git a/.clang-format b/.clang-format index 63f29c0b..8f473487 100644 --- a/.clang-format +++ b/.clang-format @@ -1,22 +1,246 @@ -Language: Cpp +# Clang format version: 18.1.3 +--- BasedOnStyle: LLVM - AccessModifierOffset: -2 -AlignConsecutiveMacros: true -AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortIfStatementsOnASingleLine: false -AllowShortLambdasOnASingleLine: Inline -BinPackArguments: false -ColumnLimit: 0 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: true + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Always +BreakAfterJavaFieldAnnotations: false +BreakArrays: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 160 +CommentPragmas: "" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 -FixNamespaceComments: false -IndentAccessModifiers: true +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: ^"(llvm|llvm-c|clang|clang-c)/ + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: ^(<|"(gtest|gmock|isl|json)/) + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: .* + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseBlocks: false IndentCaseLabels: true -IndentPPDirectives: BeforeHash +IndentExternBlock: NoIndent +IndentGotoLabels: false +IndentPPDirectives: None +IndentRequiresClause: false IndentWidth: 2 -NamespaceIndentation: All -PointerAlignment: Left -ReferenceAlignment: Left +IndentWrappedFunctionNames: true +InsertBraces: true +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtEOF: false +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +Language: Cpp +LineEnding: LF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PPIndentWidth: -1 +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDeclarationName: false + AfterFunctionDefinitionName: false + AfterIfMacros: true + AfterOverloadedOperator: true + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInContainerLiterals: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InConditionalStatements: false + InCStyleCasts: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION TabWidth: 2 UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +BracedInitializerIndentWidth: 2 diff --git a/.clang-format copy b/.clang-format copy deleted file mode 100644 index 63f29c0b..00000000 --- a/.clang-format copy +++ /dev/null @@ -1,22 +0,0 @@ -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/.codespellrc b/.codespellrc new file mode 100644 index 00000000..46c11222 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,8 @@ +[codespell] +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/spell-check/.codespellrc +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = ba,licence +skip = ./.git,./.licenses,__pycache__,.clang-format,.codespellrc,.editorconfig,.flake8,.prettierignore,.yamllint.yml,.gitignore +builtin = clear,informal,en-GB_to_en-US +check-filenames = +check-hidden = diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..e22936cb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,60 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/general/.editorconfig +# See: https://editorconfig.org/ +# The formatting style defined in this file is the official standardized style to be used in all Arduino Tooling +# projects and should not be modified. +# Note: indent style for each file type is defined even when it matches the universal config in order to make it clear +# that this type has an official style. + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{adoc,asc,asciidoc}] +indent_size = 2 +indent_style = space + +[*.{bash,sh}] +indent_size = 4 +indent_style = space + +[*.{c,cc,cp,cpp,cxx,h,hh,hpp,hxx,ii,inl,ino,ixx,pde,tpl,tpp,txx}] +indent_size = 2 +indent_style = space + +[*.{go,mod}] +indent_style = tab + +[*.java] +indent_size = 2 +indent_style = space + +[*.{js,jsx,json,jsonc,json5,ts,tsx}] +indent_size = 2 +indent_style = space + +[*.{md,mdx,mkdn,mdown,markdown}] +indent_size = unset +indent_style = space + +[*.proto] +indent_size = 2 +indent_style = space + +[*.py] +indent_size = 4 +indent_style = space + +[*.svg] +indent_size = 2 +indent_style = space + +[*.{yaml,yml}] +indent_size = 2 +indent_style = space + +[{.gitconfig,.gitmodules}] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 935eb4f5..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -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/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..02f38c33 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,2 @@ +# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/issue-report.yml b/.github/ISSUE_TEMPLATE/issue-report.yml new file mode 100644 index 00000000..c48fd1ac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue-report.yml @@ -0,0 +1,79 @@ +name: 🐛 Bug Report +description: File a bug report +labels: ["Status: Awaiting triage"] +body: + - type: markdown + attributes: + value: > + ### ⚠️ Please remember: issues are for *bugs* only! + + - type: markdown + attributes: + value: | + #### Unsure? Have a questions? 👉 [Start a new discussion](https://github.com/ESP32Async/AsyncTCP/discussions/new) + + #### Before opening a new issue, please make sure you have searched: + + - In the [documentation](https://github.com/ESP32Async/AsyncTCP) + - In the [discussions](https://github.com/ESP32Async/AsyncTCP/discussions) + - In the [issues](https://github.com/ESP32Async/AsyncTCP/issues) + - In the [examples](https://github.com/ESP32Async/AsyncTCP/tree/main/examples) + + #### Make sure you are using: + + - The [latest version of AsyncTCP](https://github.com/ESP32Async/AsyncTCP/releases) + + - type: dropdown + id: tooling + attributes: + label: IDE / Tooling + options: + - Arduino (IDE/CLI) + - pioarduino + - ESP-IDF + - PlatformIO + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: A clear and concise description of the issue. + placeholder: A clear and concise description of the issue. + validations: + required: true + + - type: textarea + id: stack-trace + attributes: + label: Stack Trace + description: Please provide a debug message or error message. If you have a Guru Meditation Error or Backtrace, [please decode it](https://maximeborges.github.io/esp-stacktrace-decoder/). + placeholder: For Arduino IDE, Enable Core debug level - Debug on tools menu, then put the serial output here. + validations: + required: true + + - type: textarea + id: how-to-reproduce + attributes: + label: Minimal Reproductible Example (MRE) + description: Post the code or the steps to reproduce the issue. + placeholder: Post the code or the steps to reproduce the issue. + validations: + required: true + + - type: checkboxes + id: confirmation + attributes: + label: "I confirm that:" + options: + - label: I have read the documentation. + required: true + - label: I have searched for similar discussions. + required: true + - label: I have searched for similar issues. + required: true + - label: I have looked at the examples. + required: true + - label: I have upgraded to the lasted version of AsyncTCP. + required: true diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 634179ca..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Question -about: Describe your question -title: "[Q]" -labels: question -assignees: '' - ---- - - diff --git a/.github/scripts/update-version.sh b/.github/scripts/update-version.sh new file mode 100755 index 00000000..95044e82 --- /dev/null +++ b/.github/scripts/update-version.sh @@ -0,0 +1,40 @@ + +#!/bin/bash +# shellcheck disable=SC2002 + +# fail the script if any command unexpectedly fails +set -e + +if [ ! $# -eq 3 ]; then + echo "Bad number of arguments: $#" >&2 + echo "usage: $0 " >&2 + exit 1 +fi + +re='^[0-9]+$' +if [[ ! $1 =~ $re ]] || [[ ! $2 =~ $re ]] || [[ ! $3 =~ $re ]] ; then + echo "error: Not a valid version: $1.$2.$3" >&2 + echo "usage: $0 " >&2 + exit 1 +fi + +ASYNCTCP_VERSION_MAJOR="$1" +ASYNCTCP_VERSION_MINOR="$2" +ASYNCTCP_VERSION_PATCH="$3" +ASYNCTCP_VERSION="$ASYNCTCP_VERSION_MAJOR.$ASYNCTCP_VERSION_MINOR.$ASYNCTCP_VERSION_PATCH" + +echo "New AsyncTCP version: $ASYNCTCP_VERSION" + +echo "Updating library.properties..." +cat library.properties | sed "s/version=.*/version=$ASYNCTCP_VERSION/g" > __library.properties && mv __library.properties library.properties + +echo "Updating library.json..." +cat library.json | sed "s/^ \"version\":.*/ \"version\": \"$ASYNCTCP_VERSION\",/g" > __library.json && mv __library.json library.json + +echo "Updating src/AsyncTCPVersion.h..." +cat src/AsyncTCPVersion.h | \ +sed "s/#define ASYNCTCP_VERSION_MAJOR.*/#define ASYNCTCP_VERSION_MAJOR $ASYNCTCP_VERSION_MAJOR/g" | \ +sed "s/#define ASYNCTCP_VERSION_MINOR.*/#define ASYNCTCP_VERSION_MINOR $ASYNCTCP_VERSION_MINOR/g" | \ +sed "s/#define ASYNCTCP_VERSION_PATCH.*/#define ASYNCTCP_VERSION_PATCH $ASYNCTCP_VERSION_PATCH/g" > src/__AsyncTCPVersion.h && mv src/__AsyncTCPVersion.h src/AsyncTCPVersion.h + +exit 0 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e4ae11f..fd8d6473 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,9 @@ name: Async TCP CI on: push: + branches: + - main + - release/* pull_request: workflow_dispatch: @@ -10,7 +13,7 @@ concurrency: cancel-in-progress: true jobs: - build-arduino: + arduino: name: ${{ matrix.config }} runs-on: ubuntu-latest strategy: @@ -59,17 +62,6 @@ jobs: - 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 diff --git a/.github/workflows/pre-commit-status.yml b/.github/workflows/pre-commit-status.yml new file mode 100644 index 00000000..d0060668 --- /dev/null +++ b/.github/workflows/pre-commit-status.yml @@ -0,0 +1,64 @@ +# This needs to be in a separate workflow because it requires higher permissions than the calling workflow +name: Report Pre-commit Check Status + +on: + workflow_run: + workflows: [Pre-commit hooks] + types: + - completed + +permissions: + statuses: write + +jobs: + report-success: + name: Report pre-commit success + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + - name: Report success + uses: actions/github-script@v7 + with: + script: | + const owner = '${{ github.repository_owner }}'; + const repo = '${{ github.repository }}'.split('/')[1]; + const sha = '${{ github.event.workflow_run.head_sha }}'; + core.debug(`owner: ${owner}`); + core.debug(`repo: ${repo}`); + core.debug(`sha: ${sha}`); + const { context: name, state } = (await github.rest.repos.createCommitStatus({ + context: 'Pre-commit checks', + description: 'Pre-commit checks successful', + owner: owner, + repo: repo, + sha: sha, + state: 'success', + target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}' + })).data; + core.info(`${name} is ${state}`); + + report-pending: + name: Report pre-commit pending + if: github.event.workflow_run.conclusion != 'success' + runs-on: ubuntu-latest + steps: + - name: Report pending + uses: actions/github-script@v7 + with: + script: | + const owner = '${{ github.repository_owner }}'; + const repo = '${{ github.repository }}'.split('/')[1]; + const sha = '${{ github.event.workflow_run.head_sha }}'; + core.debug(`owner: ${owner}`); + core.debug(`repo: ${repo}`); + core.debug(`sha: ${sha}`); + const { context: name, state } = (await github.rest.repos.createCommitStatus({ + context: 'Pre-commit checks', + description: 'The pre-commit checks need to be successful before merging', + owner: owner, + repo: repo, + sha: sha, + state: 'pending', + target_url: 'https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}' + })).data; + core.info(`${name} is ${state}`); diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..b92f283d --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,80 @@ +name: Pre-commit hooks + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize, labeled] + +concurrency: + group: pre-commit-${{github.event.pull_request.number || github.ref}} + cancel-in-progress: true + +jobs: + lint: + if: | + github.event_name != 'pull_request' || + contains(github.event.pull_request.labels.*.name, 'Status: Pending Merge') || + contains(github.event.pull_request.labels.*.name, 'Re-trigger Pre-commit') + + name: Check if fixes are needed + runs-on: ubuntu-latest + steps: + - name: Checkout latest commit + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Remove Label + if: contains(github.event.pull_request.labels.*.name, 'Re-trigger Pre-commit') + run: gh pr edit ${{ github.event.number }} --remove-label 'Re-trigger Pre-commit' + env: + GH_TOKEN: ${{ github.token }} + + - name: Set up Python 3 + uses: actions/setup-python@v5 + with: + cache-dependency-path: pre-commit.requirements.txt + cache: "pip" + python-version: "3.x" + + - name: Get Python version hash + run: | + echo "Using $(python -VV)" + echo "PY_HASH=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV + + - name: Restore pre-commit cache + uses: actions/cache/restore@v4 + id: restore-cache + with: + path: | + ~/.cache/pre-commit + key: pre-commit-${{ env.PY_HASH }}-${{ hashFiles('.pre-commit-config.yaml', '.github/workflows/pre-commit.yml', 'pre-commit.requirements.txt') }} + + - name: Install python dependencies + run: python -m pip install -r pre-commit.requirements.txt + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v42.0.2 + + - name: Run pre-commit hooks in changed files + run: pre-commit run --color=always --show-diff-on-failure --files ${{ steps.changed-files.outputs.all_changed_files }} + + - name: Save pre-commit cache + uses: actions/cache/save@v4 + if: ${{ always() && steps.restore-cache.outputs.cache-hit != 'true' }} + continue-on-error: true + with: + path: | + ~/.cache/pre-commit + key: ${{ steps.restore-cache.outputs.cache-primary-key }} + + - name: Push changes using pre-commit-ci-lite + uses: pre-commit-ci/lite-action@v1.1.0 + # Only push changes in PRs + if: ${{ always() && github.event_name == 'pull_request' }} + with: + msg: "ci(pre-commit): Apply automatic fixes" diff --git a/.github/workflows/publish-pio-registry.yml b/.github/workflows/publish-pio-registry.yml new file mode 100644 index 00000000..2961c7b1 --- /dev/null +++ b/.github/workflows/publish-pio-registry.yml @@ -0,0 +1,62 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json + +name: Publish to PlatformIO + +on: + workflow_dispatch: + inputs: + tag_name: + description: "Tag name of the release to publish (use 'latest' for the most recent tag)" + required: true + default: "latest" + +jobs: + publish: + name: Publish + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 # Ensure all commits and tags are fetched + + - name: Show latest tag + run: git tag --sort=-creatordate | head -n 1 + + - name: Download release zip + run: | + if [ "${{ inputs.tag_name }}" == "latest" ]; then + TAG_NAME=$(git tag --sort=-creatordate | head -n 1) + if [ -z "$TAG_NAME" ]; then + echo "Error: No tags found in the repository." + exit 1 + fi + else + TAG_NAME="${{ inputs.tag_name }}" + fi + echo "Downloading tag: $TAG_NAME" + curl -L -o project.zip "https://github.com/${{ github.repository }}/archive/refs/tags/$TAG_NAME.zip" + + - 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: Install PlatformIO CLI + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Publish to PlatformIO + run: pio pkg publish --no-interactive --owner ${{ github.repository_owner }} project.zip + env: + PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} diff --git a/.github/workflows/upload-idf-component.yml b/.github/workflows/upload-idf-component.yml new file mode 100644 index 00000000..71228f0f --- /dev/null +++ b/.github/workflows/upload-idf-component.yml @@ -0,0 +1,50 @@ +name: Publish ESP-IDF Component + +on: + workflow_dispatch: + inputs: + tag: + description: 'Component version (1.2.3, 1.2.3-rc1 or 1.2.3.4)' + required: true + git_ref: + description: 'Git ref with the source (branch, tag or commit)' + required: true + +permissions: + contents: read + +jobs: + upload_components: + runs-on: ubuntu-latest + steps: + - name: Get the release tag + env: + head_branch: ${{ inputs.tag || github.event.workflow_run.head_branch }} + run: | + # Read and sanitize the branch/tag name + branch=$(echo "$head_branch" | tr -cd '[:alnum:]/_.-') + + if [[ $branch == refs/tags/* ]]; then + tag="${branch#refs/tags/}" + elif [[ $branch =~ ^[v]*[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then + tag=$branch + else + echo "Tag not found in $branch. Exiting..." + exit 1 + fi + + echo "Tag: $tag" + echo "RELEASE_TAG=$tag" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref || env.RELEASE_TAG }} + submodules: "recursive" + + - name: Upload components to the component registry + uses: espressif/upload-components-ci-action@v1 + with: + name: asynctcp + version: ${{ env.RELEASE_TAG }} + namespace: esp32async + api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..660d000a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,42 @@ +exclude: | + (?x)( + ^\.github\/| + LICENSE\.md$ + ) + +default_language_version: + # force all unspecified python hooks to run python3 + python: python3 + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v5.0.0" + hooks: + # Generic checks + - id: check-case-conflict + - id: check-symlinks + - id: debug-statements + - id: destroyed-symlinks + - id: detect-private-key + - id: end-of-file-fixer + exclude: ^.*\.(bin|BIN)$ + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + exclude: ^platformio\.ini$ + + - repo: https://github.com/codespell-project/codespell + rev: "v2.3.0" + hooks: + # Spell checking + - id: codespell + exclude: ^.*\.(svd|SVD)$ + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: "v18.1.3" + hooks: + # C/C++ formatting + - id: clang-format + types_or: [c, c++] + exclude: ^.*\/build_opt\.h$ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0a5f9141..4fcdc2f5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -6,7 +6,7 @@ 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, +identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. diff --git a/README.ESP32Async.md b/README.ESP32Async.md deleted file mode 100644 index b86bcfe7..00000000 --- a/README.ESP32Async.md +++ /dev/null @@ -1,60 +0,0 @@ -# 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 f66f5050..edda6266 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,55 @@ ![https://avatars.githubusercontent.com/u/195753706?s=96&v=4](https://avatars.githubusercontent.com/u/195753706?s=96&v=4) -# Project moved to [ESP32Async](https://github.com/organizations/ESP32Async) organization at [https://github.com/ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP) +# 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) -Please see the new links: +## 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) + +## How to install + +The library can be downloaded from the releases page at [https://github.com/ESP32Async/AsyncTCP/releases](https://github.com/ESP32Async/AsyncTCP/releases). + +It is also deployed in these registries: + +- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry) + +- ESP Component Registry [https://components.espressif.com/components/esp32async/asynctcp/](https://components.espressif.com/components/esp32async/asynctcp/) + +- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/AsyncTCP](https://registry.platformio.org/libraries/esp32async/AsyncTCP) + + - Use: `lib_deps=ESP32Async/AsyncTCP` to point to latest version + - Use: `lib_deps=ESP32Async/AsyncTCP @ ^` to point to latest version with the same major version + - Use: `lib_deps=ESP32Async/AsyncTCP @ ` to always point to the same version (reproductible build) + +## 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. + +## 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) +``` + +## Compatibility -- `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) +- ESP32 +- Arduino Core 2.x and 3.x diff --git a/examples/Client/Client.ino b/examples/Client/Client.ino index 609af6db..abdfba89 100644 --- a/examples/Client/Client.ino +++ b/examples/Client/Client.ino @@ -1,8 +1,11 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + #include #include #include -// Run a server at the root of the project with: +// 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" @@ -18,24 +21,25 @@ size_t permits = MAX_CLIENTS; void makeRequest() { - if (!permits) + if (!permits) { return; + } Serial.printf("** permits: %d\n", permits); - AsyncClient* client = new AsyncClient; + AsyncClient *client = new AsyncClient; - client->onError([](void* arg, AsyncClient* client, int8_t error) { + 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) { + client->onConnect([](void *arg, AsyncClient *client) { permits--; Serial.printf("** client has been connected: %" PRIu16 "\n", client->localPort()); - client->onDisconnect([](void* arg, AsyncClient* client) { + client->onDisconnect([](void *arg, AsyncClient *client) { Serial.printf("** client has been disconnected: %" PRIu16 "\n", client->localPort()); client->close(true); delete client; @@ -44,7 +48,7 @@ void makeRequest() { makeRequest(); }); - client->onData([](void* arg, AsyncClient* client, void* data, size_t len) { + client->onData([](void *arg, AsyncClient *client, void *data, size_t len) { Serial.printf("** data received by client: %" PRIu16 ": len=%u\n", client->localPort(), len); }); @@ -59,8 +63,9 @@ void makeRequest() { void setup() { Serial.begin(115200); - while (!Serial) + while (!Serial) { continue; + } WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID); @@ -71,8 +76,9 @@ void setup() { Serial.println("** connected to WiFi"); Serial.println(WiFi.localIP()); - for (size_t i = 0; i < MAX_CLIENTS; i++) + for (size_t i = 0; i < MAX_CLIENTS; i++) { makeRequest(); + } } void loop() { diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 00000000..10c04782 --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,32 @@ +description: "Async TCP Library for ESP32 Arduino" +url: "https://github.com/ESP32Async/AsyncTCP" +license: "LGPL-3.0-or-later" +tags: + - arduino +files: + exclude: + - "idf_component_examples/" + - "idf_component_examples/**/*" + - "examples/" + - "examples/**/*" + - ".gitignore" + - ".clang-format" + - ".gitpod.Dockerfile" + - ".gitpod.yml" + - ".codespellrc" + - ".editorconfig" + - ".pre-commit-config.yaml" + - "arduino-cli.yaml" + - "arduino-cli-dev.yaml" + - "CODE_OF_CONDUCT.md" + - "component.mk" + - "library.json" + - "library.properties" + - "platformio.ini" + - "pre-commit.requirements.txt" +dependencies: + espressif/arduino-esp32: + version: "^3.1.1" + require: public +examples: + - path: ./idf_component_examples/client diff --git a/idf_component_examples/client/CMakeLists.txt b/idf_component_examples/client/CMakeLists.txt new file mode 100644 index 00000000..664d4587 --- /dev/null +++ b/idf_component_examples/client/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(main) diff --git a/idf_component_examples/client/README.md b/idf_component_examples/client/README.md new file mode 100644 index 00000000..e409b289 --- /dev/null +++ b/idf_component_examples/client/README.md @@ -0,0 +1 @@ +### Basic example to show how AsyncTCP client works diff --git a/idf_component_examples/client/main/CMakeLists.txt b/idf_component_examples/client/main/CMakeLists.txt new file mode 100644 index 00000000..9eb7ec47 --- /dev/null +++ b/idf_component_examples/client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.cpp" + INCLUDE_DIRS ".") diff --git a/idf_component_examples/client/main/idf_component.yml b/idf_component_examples/client/main/idf_component.yml new file mode 100644 index 00000000..5a8dff87 --- /dev/null +++ b/idf_component_examples/client/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +dependencies: + esp32async/asynctcp: + version: "*" + override_path: "../../../" + pre_release: true diff --git a/idf_component_examples/client/main/main.cpp b/idf_component_examples/client/main/main.cpp new file mode 100644 index 00000000..7bbbf4cc --- /dev/null +++ b/idf_component_examples/client/main/main.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +#include "Arduino.h" +#include "AsyncTCP.h" +#include "WiFi.h" + +// 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 "*********" +#define WIFI_PASS "*********" + +bool client_running = false; + +void makeRequest() { + client_running = true; + AsyncClient *client = new AsyncClient; + if (client == nullptr) { + Serial.println("** could not allocate client"); + client_running = false; + return; + } + + client->onError([](void *arg, AsyncClient *client, int8_t error) { + Serial.printf("** error occurred %s \n", client->errorToString(error)); + client->close(true); + delete client; + client_running = false; + }); + + client->onConnect([](void *arg, AsyncClient *client) { + 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; + client_running = false; + }); + + 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)) { + Serial.println("** connection failed"); + client_running = false; + } +} + +void setup() { + Serial.begin(115200); + + Serial.print("Connecting to "); + Serial.print(WIFI_SSID); + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(); + Serial.print("Connected to WiFi. IP: "); + Serial.println(WiFi.localIP()); +} + +void loop() { + if (!client_running) { + makeRequest(); + } + delay(1000); + Serial.printf("** free heap: %" PRIu32 "\n", ESP.getFreeHeap()); +} diff --git a/idf_component_examples/client/sdkconfig.defaults b/idf_component_examples/client/sdkconfig.defaults new file mode 100644 index 00000000..bb723653 --- /dev/null +++ b/idf_component_examples/client/sdkconfig.defaults @@ -0,0 +1,12 @@ +# +# Arduino ESP32 +# +CONFIG_AUTOSTART_ARDUINO=y +# end of Arduino ESP32 + +# +# FREERTOS +# +CONFIG_FREERTOS_HZ=1000 +# end of FREERTOS +# end of Component config diff --git a/library.json b/library.json index 7e41f19c..6e725736 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "AsyncTCP", - "version": "3.3.2", + "version": "3.3.5", "description": "Asynchronous TCP Library for ESP32", "keywords": "async,tcp", "repository": { diff --git a/library.properties b/library.properties index 6bde6337..8c2495f2 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=Async TCP includes=AsyncTCP.h -version=3.3.2 +version=3.3.5 author=ESP32Async maintainer=ESP32Async sentence=Async TCP Library for ESP32 diff --git a/platformio.ini b/platformio.ini index a19908a9..d72dc3f4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,5 +1,5 @@ [platformio] -default_envs = arduino-2, arduino-3, arduino-311 +default_envs = arduino-2, arduino-3 lib_dir = . src_dir = examples/Client @@ -20,24 +20,17 @@ monitor_filters = esp32_exception_decoder, log2file board = esp32dev [env:arduino-2] -platform = espressif32@6.9.0 +platform = espressif32@6.10.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 +platform = espressif32@6.10.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/pre-commit.requirements.txt b/pre-commit.requirements.txt new file mode 100644 index 00000000..40a16fa9 --- /dev/null +++ b/pre-commit.requirements.txt @@ -0,0 +1 @@ +pre-commit==4.1.0 diff --git a/src/AsyncTCP.cpp b/src/AsyncTCP.cpp index 07611447..a92ed1a6 100644 --- a/src/AsyncTCP.cpp +++ b/src/AsyncTCP.cpp @@ -1,23 +1,5 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov #include "Arduino.h" @@ -32,32 +14,32 @@ extern "C" { } #if CONFIG_ASYNC_TCP_USE_WDT - #include "esp_task_wdt.h" +#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 +#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_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 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 @@ -81,53 +63,54 @@ typedef enum { LWIP_TCP_ACCEPT, LWIP_TCP_CONNECTED, LWIP_TCP_DNS -} lwip_event_t; +} lwip_tcp_event_t; typedef struct { - 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; + lwip_tcp_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_tcp_event_packet_t; + +static QueueHandle_t _async_queue = NULL; static TaskHandle_t _async_service_task_handle = NULL; -SemaphoreHandle_t _slots_lock; -const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP; +static SemaphoreHandle_t _slots_lock = NULL; +static 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(); + configASSERT(_slots_lock); // Add sanity check xSemaphoreGive(_slots_lock); for (int i = 0; i < _number_of_closed_slots; ++i) { _closed_slots[i] = 1; @@ -137,7 +120,7 @@ static uint32_t _closed_index = []() { static inline bool _init_async_event_queue() { if (!_async_queue) { - _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE_SIZE, sizeof(lwip_event_packet_t*)); + _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE_SIZE, sizeof(lwip_tcp_event_packet_t *)); if (!_async_queue) { return false; } @@ -145,82 +128,86 @@ static inline bool _init_async_event_queue() { return true; } -static inline bool _send_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) { +static inline bool _send_async_event(lwip_tcp_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, TickType_t wait = portMAX_DELAY) { +static inline bool _prepend_async_event(lwip_tcp_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) { - if (!_async_queue) { - return false; - } +static inline bool _get_async_event(lwip_tcp_event_packet_t **e) { + while (true) { + if (!_async_queue) { + break; + } #if CONFIG_ASYNC_TCP_USE_WDT - // need to return periodically to feed the dog - if (xQueueReceive(_async_queue, e, pdMS_TO_TICKS(1000)) != pdPASS) - return false; + // need to return periodically to feed the dog + if (xQueueReceive(_async_queue, e, pdMS_TO_TICKS(1000)) != pdPASS) { + break; + } #else - if (xQueueReceive(_async_queue, e, portMAX_DELAY) != pdPASS) - return false; + if (xQueueReceive(_async_queue, e, portMAX_DELAY) != pdPASS) { + break; + } #endif - if ((*e)->event != LWIP_TCP_POLL) - return true; + 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; + /* + 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 dequeuing or (better) simply punish user for a bad designed callbacks by resetting hog connections + */ + lwip_tcp_event_packet_t *next_pkt = NULL; + while (xQueuePeek(_async_queue, &next_pkt, 0) == pdPASS) { + // if the next event that will come is a poll event for the same connection, we can discard it and continue + 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; + } } - } - // quit while loop if next event can't be discarded - break; - } + // quit while loop if next incoming event can't be discarded (not a poll event) + 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); + /* + 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"); + continue; // continue main loop to dequeue next event which we know is not a poll event + } + return true; // queue not nearly full, caller can process the poll event } - - // last resort return - return true; + return false; } -static bool _remove_events_with_arg(void* arg) { +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; + lwip_tcp_event_packet_t *first_packet = NULL; + lwip_tcp_event_packet_t *packet = NULL; // figure out which is the first non-matching packet so we can keep the order while (!first_packet) { @@ -228,7 +215,7 @@ static bool _remove_events_with_arg(void* arg) { return false; } // discard packet if matching - if ((int)first_packet->arg == (int)arg) { + if ((uintptr_t)first_packet->arg == (uintptr_t)arg) { free(first_packet); first_packet = NULL; } else if (xQueueSend(_async_queue, &first_packet, 0) != pdPASS) { @@ -245,7 +232,7 @@ static bool _remove_events_with_arg(void* arg) { if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) { return false; } - if ((int)packet->arg == (int)arg) { + if ((uintptr_t)packet->arg == (uintptr_t)arg) { // remove matching event free(packet); packet = NULL; @@ -261,7 +248,7 @@ static bool _remove_events_with_arg(void* arg) { return true; } -static void _handle_async_event(lwip_event_packet_t* e) { +static void _handle_async_event(lwip_tcp_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); @@ -292,16 +279,16 @@ static void _handle_async_event(lwip_event_packet_t* e) { // 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)); + free((void *)(e)); } -static void _async_service_task(void* pvParameters) { +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; + lwip_tcp_event_packet_t *packet = NULL; for (;;) { if (_get_async_event(&packet)) { _handle_async_event(packet); @@ -326,13 +313,9 @@ static void _stop_async_task(){ */ 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) { + 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); @@ -349,7 +332,9 @@ static bool _start_async_task() { 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); + 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; } @@ -361,31 +346,41 @@ static bool _start_async_task() { * 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)); +static int8_t _tcp_clear_events(void *arg) { + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } e->event = LWIP_TCP_CLEAR; e->arg = arg; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); + return ERR_TIMEOUT; } return ERR_OK; } -static int8_t _tcp_connected(void* arg, tcp_pcb* pcb, int8_t err) { +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)); + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } e->event = LWIP_TCP_CONNECTED; e->arg = arg; e->connected.pcb = pcb; e->connected.err = err; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); + return ERR_TIMEOUT; } 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 +static int8_t _tcp_poll(void *arg, struct tcp_pcb *pcb) { + // throttle polling events queueing 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"); @@ -393,19 +388,28 @@ 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)); + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } 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)); + free((void *)(e)); + return ERR_TIMEOUT; } 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)); +static int8_t _tcp_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *pb, int8_t err) { + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } e->arg = arg; if (pb) { // ets_printf("+R: 0x%08x\n", pcb); @@ -422,37 +426,51 @@ static int8_t _tcp_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err); } if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); + return ERR_TIMEOUT; } return ERR_OK; } -static int8_t _tcp_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) { +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)); + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } e->event = LWIP_TCP_SENT; e->arg = arg; e->sent.pcb = pcb; e->sent.len = len; if (!_send_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); + return ERR_TIMEOUT; } return ERR_OK; } -static void _tcp_error(void* arg, int8_t err) { +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)); + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return; + } e->event = LWIP_TCP_ERROR; e->arg = arg; e->error.err = err; if (!_send_async_event(&e)) { - free((void*)(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)); +static void _tcp_dns_found(const char *name, struct ip_addr *ipaddr, void *arg) { + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return; + } // ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg); e->event = LWIP_TCP_DNS; e->arg = arg; @@ -463,18 +481,23 @@ static void _tcp_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) memset(&e->dns.addr, 0, sizeof(e->dns.addr)); } if (!_send_async_event(&e)) { - free((void*)(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)); +static int8_t _tcp_accept(void *arg, AsyncClient *client) { + lwip_tcp_event_packet_t *e = (lwip_tcp_event_packet_t *)malloc(sizeof(lwip_tcp_event_packet_t)); + if (!e) { + log_e("Failed to allocate event packet"); + return ERR_MEM; + } e->event = LWIP_TCP_ACCEPT; e->arg = arg; e->accept.client = client; if (!_prepend_async_event(&e)) { - free((void*)(e)); + free((void *)(e)); + return ERR_TIMEOUT; } return ERR_OK; } @@ -486,32 +509,32 @@ static int8_t _tcp_accept(void* arg, AsyncClient* client) { #include "lwip/priv/tcpip_priv.h" typedef struct { - struct tcpip_api_call_data call; - 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 tcpip_api_call_data call; + 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; + }; } 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; +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); @@ -519,19 +542,19 @@ static err_t _tcp_output_api(struct tcpip_api_call_data* api_call_msg) { return msg->err; } -static esp_err_t _tcp_output(tcp_pcb* pcb, int8_t closed_slot) { +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); + 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; +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); @@ -539,7 +562,7 @@ static err_t _tcp_write_api(struct tcpip_api_call_data* api_call_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) { +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; } @@ -549,12 +572,12 @@ static esp_err_t _tcp_write(tcp_pcb* pcb, int8_t closed_slot, const char* data, msg.write.data = data; msg.write.size = size; msg.write.apiflags = apiflags; - tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg); + 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; +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]) { @@ -565,7 +588,7 @@ static err_t _tcp_recved_api(struct tcpip_api_call_data* api_call_msg) { return msg->err; } -static esp_err_t _tcp_recved(tcp_pcb* pcb, int8_t closed_slot, size_t len) { +static esp_err_t _tcp_recved(tcp_pcb *pcb, int8_t closed_slot, size_t len) { if (!pcb) { return ERR_CONN; } @@ -573,12 +596,12 @@ static esp_err_t _tcp_recved(tcp_pcb* pcb, int8_t closed_slot, size_t len) { msg.pcb = pcb; msg.closed_slot = closed_slot; msg.received = len; - tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg); + 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; +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); @@ -586,19 +609,19 @@ static err_t _tcp_close_api(struct tcpip_api_call_data* api_call_msg) { return msg->err; } -static esp_err_t _tcp_close(tcp_pcb* pcb, int8_t closed_slot) { +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); + 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; +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); @@ -606,24 +629,24 @@ static err_t _tcp_abort_api(struct tcpip_api_call_data* api_call_msg) { return msg->err; } -static esp_err_t _tcp_abort(tcp_pcb* pcb, int8_t closed_slot) { +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); + 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; +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) { +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; } @@ -633,17 +656,17 @@ static esp_err_t _tcp_connect(tcp_pcb* pcb, int8_t closed_slot, ip_addr_t* addr, msg.connect.addr = addr; msg.connect.port = port; msg.connect.cb = cb; - tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg); + 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; +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) { +static esp_err_t _tcp_bind(tcp_pcb *pcb, ip_addr_t *addr, uint16_t port) { if (!pcb) { return ESP_FAIL; } @@ -652,18 +675,18 @@ static esp_err_t _tcp_bind(tcp_pcb* pcb, ip_addr_t* addr, uint16_t port) { msg.closed_slot = -1; msg.bind.addr = addr; msg.bind.port = port; - tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg); + 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_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) { +static tcp_pcb *_tcp_listen_with_backlog(tcp_pcb *pcb, uint8_t backlog) { if (!pcb) { return NULL; } @@ -671,7 +694,7 @@ static tcp_pcb* _tcp_listen_with_backlog(tcp_pcb* pcb, uint8_t backlog) { msg.pcb = pcb; msg.closed_slot = -1; msg.backlog = backlog ? backlog : 0xFF; - tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg); + tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data *)&msg); return msg.pcb; } @@ -679,8 +702,10 @@ static tcp_pcb* _tcp_listen_with_backlog(tcp_pcb* pcb, uint8_t backlog) { 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), _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) { +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), _poll_cb(0), _poll_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) { @@ -707,7 +732,7 @@ AsyncClient::~AsyncClient() { * Operators * */ -AsyncClient& AsyncClient::operator=(const AsyncClient& other) { +AsyncClient &AsyncClient::operator=(const AsyncClient &other) { if (_pcb) { _close(); } @@ -725,20 +750,20 @@ AsyncClient& AsyncClient::operator=(const AsyncClient& other) { return *this; } -bool AsyncClient::operator==(const AsyncClient& other) { +bool AsyncClient::operator==(const AsyncClient &other) { return _pcb == other._pcb; } -AsyncClient& AsyncClient::operator+=(const AsyncClient& other) { +AsyncClient &AsyncClient::operator+=(const AsyncClient &other) { if (next == NULL) { - next = (AsyncClient*)(&other); + next = (AsyncClient *)(&other); next->prev = this; } else { - AsyncClient* c = next; + AsyncClient *c = next; while (c->next != NULL) { c = c->next; } - c->next = (AsyncClient*)(&other); + c->next = (AsyncClient *)(&other); c->next->prev = c; } return *this; @@ -748,42 +773,42 @@ AsyncClient& AsyncClient::operator+=(const AsyncClient& other) { * Callback Setters * */ -void AsyncClient::onConnect(AcConnectHandler cb, void* arg) { +void AsyncClient::onConnect(AcConnectHandler cb, void *arg) { _connect_cb = cb; _connect_cb_arg = arg; } -void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg) { +void AsyncClient::onDisconnect(AcConnectHandler cb, void *arg) { _discard_cb = cb; _discard_cb_arg = arg; } -void AsyncClient::onAck(AcAckHandler cb, void* arg) { +void AsyncClient::onAck(AcAckHandler cb, void *arg) { _sent_cb = cb; _sent_cb_arg = arg; } -void AsyncClient::onError(AcErrorHandler cb, void* arg) { +void AsyncClient::onError(AcErrorHandler cb, void *arg) { _error_cb = cb; _error_cb_arg = arg; } -void AsyncClient::onData(AcDataHandler cb, void* 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) { +void AsyncClient::onTimeout(AcTimeoutHandler cb, void *arg) { _timeout_cb = cb; _timeout_cb_arg = arg; } -void AsyncClient::onPoll(AcConnectHandler cb, void* arg) { +void AsyncClient::onPoll(AcConnectHandler cb, void *arg) { _poll_cb = cb; _poll_cb_arg = arg; } @@ -808,7 +833,7 @@ bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { } TCP_MUTEX_LOCK(); - tcp_pcb* pcb = tcp_new_ip_type(addr.type); + tcp_pcb *pcb = tcp_new_ip_type(addr.type); if (!pcb) { TCP_MUTEX_UNLOCK(); log_e("pcb == NULL"); @@ -825,7 +850,7 @@ bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) { return err == ESP_OK; } -bool AsyncClient::connect(const IPAddress& ip, uint16_t port) { +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; @@ -838,15 +863,15 @@ bool AsyncClient::connect(const IPAddress& ip, uint16_t port) { } #if LWIP_IPV6 && ESP_IDF_VERSION_MAJOR < 5 -bool AsyncClient::connect(const IPv6Address& ip, uint16_t port) { - auto ipaddr = static_cast(ip); +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) { +bool AsyncClient::connect(const char *host, uint16_t port) { ip_addr_t addr; if (!_start_async_task()) { @@ -859,14 +884,14 @@ bool AsyncClient::connect(const char* host, uint16_t port) { TCP_MUTEX_UNLOCK(); if (err == ERR_OK) { #if ESP_IDF_VERSION_MAJOR < 5 - #if LWIP_IPV6 +#if LWIP_IPV6 if (addr.type == IPADDR_TYPE_V6) { return connect(IPv6Address(addr.u_addr.ip6.addr), port); } return connect(IPAddress(addr.u_addr.ip4.addr), port); - #else +#else return connect(IPAddress(addr.addr), port); - #endif +#endif #else return _connect(addr, port); #endif @@ -900,7 +925,7 @@ size_t AsyncClient::space() { return 0; } -size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) { +size_t AsyncClient::add(const char *data, size_t size, uint8_t apiflags) { if (!_pcb || size == 0 || data == NULL) { return 0; } @@ -928,8 +953,9 @@ bool AsyncClient::send() { } size_t AsyncClient::ack(size_t len) { - if (len > _rx_ack_len) + if (len > _rx_ack_len) { len = _rx_ack_len; + } if (len) { _tcp_recved(_pcb, _closed_slot, len); } @@ -937,7 +963,7 @@ size_t AsyncClient::ack(size_t len) { return len; } -void AsyncClient::ackPacket(struct pbuf* pb) { +void AsyncClient::ackPacket(struct pbuf *pb) { if (!pb) { return; } @@ -975,22 +1001,25 @@ int8_t AsyncClient::_close() { } 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; + bool allocated = false; + if (xSemaphoreTake(_slots_lock, portMAX_DELAY) == pdTRUE) { + uint32_t closed_slot_min_index = 0; + allocated = _closed_slot != INVALID_CLOSED_SLOT; + if (!allocated) { + 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; + } + } + allocated = _closed_slot != INVALID_CLOSED_SLOT; + if (allocated) { + _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); + return allocated; } void AsyncClient::_free_closed_slot() { @@ -1007,8 +1036,8 @@ void AsyncClient::_free_closed_slot() { * Private Callbacks * */ -int8_t AsyncClient::_connected(tcp_pcb* pcb, int8_t err) { - _pcb = reinterpret_cast(pcb); +int8_t AsyncClient::_connected(tcp_pcb *pcb, int8_t err) { + _pcb = reinterpret_cast(pcb); if (_pcb) { _rx_last_packet = millis(); } @@ -1041,7 +1070,7 @@ void AsyncClient::_error(int8_t err) { } // In LwIP Thread -int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { +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; @@ -1062,7 +1091,7 @@ int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) { } // In Async Thread -int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { +int8_t AsyncClient::_fin(tcp_pcb *pcb, int8_t err) { _tcp_clear_events(this); if (_discard_cb) { _discard_cb(_discard_cb_arg, this); @@ -1070,7 +1099,7 @@ int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) { return ERR_OK; } -int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { +int8_t AsyncClient::_sent(tcp_pcb *pcb, uint16_t len) { _rx_last_ack = _rx_last_packet = millis(); if (_sent_cb) { _sent_cb(_sent_cb_arg, this, len, (_rx_last_packet - _tx_last_packet)); @@ -1078,12 +1107,12 @@ int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) { return ERR_OK; } -int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { +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; + pbuf *b = pb; pb = b->next; b->next = NULL; if (_pb_cb) { @@ -1103,7 +1132,7 @@ int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) { return ERR_OK; } -int8_t AsyncClient::_poll(tcp_pcb* pcb) { +int8_t AsyncClient::_poll(tcp_pcb *pcb) { if (!_pcb) { // log_d("pcb is NULL"); return ERR_OK; @@ -1121,8 +1150,9 @@ int8_t AsyncClient::_poll(tcp_pcb* pcb) { 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) + if (_timeout_cb) { _timeout_cb(_timeout_cb_arg, this, (now - _tx_last_packet)); + } return ERR_OK; } } @@ -1139,14 +1169,14 @@ int8_t AsyncClient::_poll(tcp_pcb* pcb) { return ERR_OK; } -void AsyncClient::_dns_found(struct ip_addr* ipaddr) { +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 +#if LWIP_IPV6 } else if (ipaddr && ipaddr->u_addr.ip6.addr) { connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port); - #endif +#endif #else if (ipaddr) { IPAddress ip; @@ -1177,7 +1207,7 @@ bool AsyncClient::free() { return false; } -size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) { +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; @@ -1221,13 +1251,13 @@ bool AsyncClient::getNoDelay() { 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 + _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 + _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 + _pcb->so_options &= ~SOF_KEEPALIVE; // Turn off TCP Keepalive for the given pcb } } @@ -1267,7 +1297,7 @@ ip6_addr_t AsyncClient::getLocalAddress6() { } return _pcb->local_ip.u_addr.ip6; } - #if ESP_IDF_VERSION_MAJOR < 5 +#if ESP_IDF_VERSION_MAJOR < 5 IPv6Address AsyncClient::remoteIP6() { return IPv6Address(getRemoteAddress6().addr); } @@ -1275,7 +1305,7 @@ IPv6Address AsyncClient::remoteIP6() { IPv6Address AsyncClient::localIP6() { return IPv6Address(getLocalAddress6().addr); } - #else +#else IPAddress AsyncClient::remoteIP6() { if (!_pcb) { return IPAddress(IPType::IPv6); @@ -1293,7 +1323,7 @@ IPAddress AsyncClient::localIP6() { ip.from_ip_addr_t(&(_pcb->local_ip)); return ip; } - #endif +#endif #endif uint16_t AsyncClient::getRemotePort() { @@ -1401,73 +1431,43 @@ bool AsyncClient::canSend() { return space() > 0; } -const char* AsyncClient::errorToString(int8_t error) { +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() { + 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"; + 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"; } } @@ -1475,36 +1475,36 @@ const char* AsyncClient::stateToString() { * 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, struct tcp_pcb* 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); } /* @@ -1512,38 +1512,38 @@ int8_t AsyncClient::_s_connected(void* arg, struct tcp_pcb* pcb, int8_t err) { */ AsyncServer::AsyncServer(IPAddress addr, uint16_t port) - : _port(port) + : _port(port) #if ESP_IDF_VERSION_MAJOR < 5 - , - _bind4(true), _bind6(false) + , + _bind4(true), _bind6(false) #else - , - _bind4(addr.type() != IPType::IPv6), _bind6(addr.type() == IPType::IPv6) + , + _bind4(addr.type() != IPType::IPv6), _bind6(addr.type() == IPType::IPv6) #endif - , - _addr(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { + , + _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) {} + : _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), _bind4(true), _bind6(false), _addr((uint32_t)IPADDR_ANY) + : _port(port), _bind4(true), _bind6(false), _addr((uint32_t)IPADDR_ANY) #if ESP_IDF_VERSION_MAJOR < 5 - , - _addr6() + , + _addr6() #endif - , - _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { + , + _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) { } AsyncServer::~AsyncServer() { end(); } -void AsyncServer::onClient(AcConnectHandler cb, void* arg) { +void AsyncServer::onClient(AcConnectHandler cb, void *arg) { _connect_cb = cb; _connect_cb_arg = arg; } @@ -1568,9 +1568,9 @@ void AsyncServer::begin() { 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 + 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); + 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 = _addr; @@ -1582,6 +1582,7 @@ void AsyncServer::begin() { if (err != ERR_OK) { _tcp_close(_pcb, -1); + _pcb = NULL; log_e("bind error: %d", err); return; } @@ -1593,7 +1594,7 @@ void AsyncServer::begin() { return; } TCP_MUTEX_LOCK(); - tcp_arg(_pcb, (void*)this); + tcp_arg(_pcb, (void *)this); tcp_accept(_pcb, &_s_accept); TCP_MUTEX_UNLOCK(); } @@ -1614,23 +1615,26 @@ void AsyncServer::end() { } // runs on LwIP thread -int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err) { +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); + AsyncClient *c = new (std::nothrow) AsyncClient(pcb); if (c) { c->setNoDelay(_noDelay); - return _tcp_accept(this, c); + const int8_t err = _tcp_accept(this, c); + if (err != ERR_OK) { + tcp_abort(pcb); + delete c; + } + return err; } } - if (tcp_close(pcb) != ERR_OK) { - tcp_abort(pcb); - } - log_d("FAIL"); + tcp_abort(pcb); + log_d("_accept failed"); return ERR_OK; } -int8_t AsyncServer::_accepted(AsyncClient* client) { +int8_t AsyncServer::_accepted(AsyncClient *client) { if (_connect_cb) { _connect_cb(_connect_cb_arg, client); } @@ -1652,10 +1656,10 @@ uint8_t AsyncServer::status() { 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 431785fd..389692fd 100644 --- a/src/AsyncTCP.h +++ b/src/AsyncTCP.h @@ -1,131 +1,112 @@ -/* - Asynchronous TCP library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov #ifndef ASYNCTCP_H_ #define ASYNCTCP_H_ -#define ASYNCTCP_VERSION "3.3.2" -#define ASYNCTCP_VERSION_MAJOR 3 -#define ASYNCTCP_VERSION_MINOR 3 -#define ASYNCTCP_VERSION_REVISION 2 +#include "AsyncTCPVersion.h" #define ASYNCTCP_FORK_ESP32Async #include "IPAddress.h" #if ESP_IDF_VERSION_MAJOR < 5 - #include "IPv6Address.h" +#include "IPv6Address.h" #endif #include "lwip/ip6_addr.h" #include "lwip/ip_addr.h" #include #ifndef LIBRETINY - #include "sdkconfig.h" +#include "sdkconfig.h" extern "C" { - #include "freertos/semphr.h" - #include "lwip/pbuf.h" +#include "freertos/semphr.h" +#include "lwip/pbuf.h" } #else extern "C" { - #include - #include +#include +#include } - #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core +#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_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 +#define CONFIG_ASYNC_TCP_USE_WDT 1 #endif #ifndef CONFIG_ASYNC_TCP_STACK_SIZE - #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 +#define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2 #endif #ifndef CONFIG_ASYNC_TCP_PRIORITY - #define CONFIG_ASYNC_TCP_PRIORITY 10 +#define CONFIG_ASYNC_TCP_PRIORITY 10 #endif #ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE - #define CONFIG_ASYNC_TCP_QUEUE_SIZE 64 +#define CONFIG_ASYNC_TCP_QUEUE_SIZE 64 #endif #ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME - #define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 +#define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000 #endif class AsyncClient; -#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 AcTimeoutHandler; +typedef std::function AcConnectHandler; +typedef std::function AcAckHandler; +typedef std::function AcErrorHandler; +typedef std::function AcDataHandler; +typedef std::function AcPacketHandler; +typedef std::function AcTimeoutHandler; struct tcp_pcb; struct ip_addr; class AsyncClient { - public: - AsyncClient(tcp_pcb* pcb = 0); - ~AsyncClient(); +public: + 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) { - return !(*this == other); - } - bool connect(const IPAddress& ip, uint16_t port); + bool operator!=(const AsyncClient &other) { + return !(*this == other); + } + bool connect(const IPAddress &ip, uint16_t port); #if ESP_IDF_VERSION_MAJOR < 5 - bool connect(const IPv6Address& ip, uint16_t port); + bool connect(const IPv6Address &ip, uint16_t port); #endif - bool connect(const char* host, uint16_t port); - /** + bool connect(const char *host, uint16_t port); + /** * @brief close connection * * @param now - ignored */ - void close(bool now = false); - // same as close() - void stop() { close(false); }; - int8_t abort(); - bool free(); - - // ack is not pending - bool canSend(); - // TCP buffer space available - size_t space(); - - /** + void close(bool now = false); + // same as close() + void stop() { + close(false); + }; + int8_t abort(); + bool free(); + + // ack is not pending + bool canSend(); + // TCP buffer space available + size_t space(); + + /** * @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 @@ -140,17 +121,17 @@ class AsyncClient { * @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); + 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(); + bool send(); - /** + /** * @brief add and enqueue data for sending * @note it is same as add() + send() * @note only make sense when canSend() == true @@ -160,185 +141,192 @@ class AsyncClient { * @param apiflags * @return size_t */ - size_t write(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); + size_t write(const char *data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY); - /** - * @brief add and enque data for sending + /** + * @brief add and enqueue 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)); }; + 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(); + uint8_t state(); + bool connecting(); + bool connected(); + bool disconnecting(); + bool disconnected(); - // disconnected or disconnecting - bool freeable(); + // disconnected or disconnecting + bool freeable(); - uint16_t getMss(); + uint16_t getMss(); - uint32_t getRxTimeout(); - // no RX data timeout for the connection in seconds - void setRxTimeout(uint32_t timeout); + uint32_t getRxTimeout(); + // no RX data timeout for the connection in seconds + void setRxTimeout(uint32_t timeout); - uint32_t getAckTimeout(); - // no ACK timeout for the last sent packet in milliseconds - void setAckTimeout(uint32_t timeout); + uint32_t getAckTimeout(); + // no ACK timeout for the last sent packet in milliseconds + void setAckTimeout(uint32_t timeout); - void setNoDelay(bool nodelay); - bool getNoDelay(); + void setNoDelay(bool nodelay); + bool getNoDelay(); - void setKeepAlive(uint32_t ms, uint8_t cnt); + void setKeepAlive(uint32_t ms, uint8_t cnt); - uint32_t getRemoteAddress(); - uint16_t getRemotePort(); - uint32_t getLocalAddress(); - uint16_t getLocalPort(); + 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 + 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 - IPAddress remoteIP(); - uint16_t remotePort(); - IPAddress localIP(); - uint16_t localPort(); - - // 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); - - // 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; } - - static const char* errorToString(int8_t error); - const char* stateToString(); - - // 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; } - - protected: - bool _connect(ip_addr_t addr, uint16_t port); - - tcp_pcb* _pcb; - int8_t _closed_slot; - - AcConnectHandler _connect_cb; - void* _connect_cb_arg; - AcConnectHandler _discard_cb; - void* _discard_cb_arg; - AcAckHandler _sent_cb; - void* _sent_cb_arg; - AcErrorHandler _error_cb; - void* _error_cb_arg; - AcDataHandler _recv_cb; - void* _recv_cb_arg; - AcPacketHandler _pb_cb; - void* _pb_cb_arg; - AcTimeoutHandler _timeout_cb; - void* _timeout_cb_arg; - AcConnectHandler _poll_cb; - void* _poll_cb_arg; - - bool _ack_pcb; - uint32_t _tx_last_packet; - uint32_t _rx_ack_len; - uint32_t _rx_last_packet; - uint32_t _rx_timeout; - uint32_t _rx_last_ack; - uint32_t _ack_timeout; - uint16_t _connect_port; - - int8_t _close(); - void _free_closed_slot(); - 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); - - public: - AsyncClient* prev; - AsyncClient* next; + // compatibility + IPAddress remoteIP(); + uint16_t remotePort(); + IPAddress localIP(); + uint16_t localPort(); + + // 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 + // !!! You MUST call ackPacket() or free the pbuf yourself to prevent memory leaks + 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); + + // 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; + } + + static const char *errorToString(int8_t error); + const char *stateToString(); + + // 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; + } + +protected: + bool _connect(ip_addr_t addr, uint16_t port); + + tcp_pcb *_pcb; + int8_t _closed_slot; + + AcConnectHandler _connect_cb; + void *_connect_cb_arg; + AcConnectHandler _discard_cb; + void *_discard_cb_arg; + AcAckHandler _sent_cb; + void *_sent_cb_arg; + AcErrorHandler _error_cb; + void *_error_cb_arg; + AcDataHandler _recv_cb; + void *_recv_cb_arg; + AcPacketHandler _pb_cb; + void *_pb_cb_arg; + AcTimeoutHandler _timeout_cb; + void *_timeout_cb_arg; + AcConnectHandler _poll_cb; + void *_poll_cb_arg; + + bool _ack_pcb; + uint32_t _tx_last_packet; + uint32_t _rx_ack_len; + uint32_t _rx_last_packet; + uint32_t _rx_timeout; + uint32_t _rx_last_ack; + uint32_t _ack_timeout; + uint16_t _connect_port; + + int8_t _close(); + void _free_closed_slot(); + 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); + +public: + AsyncClient *prev; + AsyncClient *next; }; class AsyncServer { - public: - AsyncServer(IPAddress addr, uint16_t port); +public: + AsyncServer(IPAddress addr, uint16_t port); #if ESP_IDF_VERSION_MAJOR < 5 - AsyncServer(IPv6Address addr, uint16_t port); + AsyncServer(IPv6Address addr, uint16_t port); #endif - AsyncServer(uint16_t port); - ~AsyncServer(); - void onClient(AcConnectHandler cb, void* arg); - void begin(); - void end(); - void setNoDelay(bool nodelay); - 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); - - protected: - uint16_t _port; - bool _bind4 = false; - bool _bind6 = false; - IPAddress _addr; + AsyncServer(uint16_t port); + ~AsyncServer(); + void onClient(AcConnectHandler cb, void *arg); + void begin(); + void end(); + void setNoDelay(bool nodelay); + 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); + +protected: + uint16_t _port; + bool _bind4 = false; + bool _bind6 = false; + IPAddress _addr; #if ESP_IDF_VERSION_MAJOR < 5 - IPv6Address _addr6; + IPv6Address _addr6; #endif - bool _noDelay; - tcp_pcb* _pcb; - AcConnectHandler _connect_cb; - void* _connect_cb_arg; + bool _noDelay; + tcp_pcb *_pcb; + AcConnectHandler _connect_cb; + void *_connect_cb_arg; - int8_t _accept(tcp_pcb* newpcb, int8_t err); - int8_t _accepted(AsyncClient* client); + int8_t _accept(tcp_pcb *newpcb, int8_t err); + int8_t _accepted(AsyncClient *client); }; #endif /* ASYNCTCP_H_ */ diff --git a/src/AsyncTCPVersion.h b/src/AsyncTCPVersion.h new file mode 100644 index 00000000..f2c4c377 --- /dev/null +++ b/src/AsyncTCPVersion.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** Major version number (X.x.x) */ +#define ASYNCTCP_VERSION_MAJOR 3 +/** Minor version number (x.X.x) */ +#define ASYNCTCP_VERSION_MINOR 3 +/** Patch version number (x.x.X) */ +#define ASYNCTCP_VERSION_PATCH 5 + +/** + * Macro to convert version number into an integer + * + * To be used in comparisons, such as ASYNCTCP_VERSION >= ASYNCTCP_VERSION_VAL(2, 0, 0) + */ +#define ASYNCTCP_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) + +/** + * Current version, as an integer + * + * To be used in comparisons, such as ASYNCTCP_VERSION_NUM >= ASYNCTCP_VERSION_VAL(2, 0, 0) + */ +#define ASYNCTCP_VERSION_NUM ASYNCTCP_VERSION_VAL(ASYNCTCP_VERSION_MAJOR, ASYNCTCP_VERSION_MINOR, ASYNCTCP_VERSION_PATCH) + +/** + * Current version, as string + */ +#define df2xstr(s) #s +#define df2str(s) df2xstr(s) +#define ASYNCTCP_VERSION df2str(ASYNCTCP_VERSION_MAJOR) "." df2str(ASYNCTCP_VERSION_MINOR) "." df2str(ASYNCTCP_VERSION_PATCH) + +#ifdef __cplusplus +} +#endif