├── .clang-format
├── .clang-format copy
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── question.md
├── dependabot.yml
├── stale.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitpod.Dockerfile
├── .gitpod.yml
├── CMakeLists.txt
├── CODE_OF_CONDUCT.md
├── Kconfig.projbuild
├── LICENSE
├── README.ESP32Async.md
├── README.md
├── arduino-cli-dev.yaml
├── arduino-cli.yaml
├── component.mk
├── examples
└── Client
│ └── Client.ino
├── library.json
├── library.properties
├── platformio.ini
└── src
├── AsyncTCP.cpp
└── AsyncTCP.h
/.clang-format:
--------------------------------------------------------------------------------
1 | Language: Cpp
2 | BasedOnStyle: LLVM
3 |
4 | AccessModifierOffset: -2
5 | AlignConsecutiveMacros: true
6 | AllowAllArgumentsOnNextLine: false
7 | AllowAllParametersOfDeclarationOnNextLine: false
8 | AllowShortIfStatementsOnASingleLine: false
9 | AllowShortLambdasOnASingleLine: Inline
10 | BinPackArguments: false
11 | ColumnLimit: 0
12 | ContinuationIndentWidth: 2
13 | FixNamespaceComments: false
14 | IndentAccessModifiers: true
15 | IndentCaseLabels: true
16 | IndentPPDirectives: BeforeHash
17 | IndentWidth: 2
18 | NamespaceIndentation: All
19 | PointerAlignment: Left
20 | ReferenceAlignment: Left
21 | TabWidth: 2
22 | UseTab: Never
23 |
--------------------------------------------------------------------------------
/.clang-format copy:
--------------------------------------------------------------------------------
1 | Language: Cpp
2 | BasedOnStyle: LLVM
3 |
4 | AccessModifierOffset: -2
5 | AlignConsecutiveMacros: true
6 | AllowAllArgumentsOnNextLine: false
7 | AllowAllParametersOfDeclarationOnNextLine: false
8 | AllowShortIfStatementsOnASingleLine: false
9 | AllowShortLambdasOnASingleLine: Inline
10 | BinPackArguments: false
11 | ColumnLimit: 0
12 | ContinuationIndentWidth: 2
13 | FixNamespaceComments: false
14 | IndentAccessModifiers: true
15 | IndentCaseLabels: true
16 | IndentPPDirectives: BeforeHash
17 | IndentWidth: 2
18 | NamespaceIndentation: All
19 | PointerAlignment: Left
20 | ReferenceAlignment: Left
21 | TabWidth: 2
22 | UseTab: Never
23 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Please make sure to go through the recommendations before opening a bug report:**
11 |
12 | [https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations](https://github.com/ESP32Async/AsyncTCP?tab=readme-ov-file#important-recommendations)
13 |
14 | **Description**
15 |
16 | A clear and concise description of what the bug is.
17 |
18 | **Board**
19 |
20 | esp32dev, esp32s3, etc
21 |
22 | **Ethernet adapter used ?**
23 |
24 | If yes, please specify which one
25 |
26 | **Stack trace**
27 |
28 | Please provide the stack trace here taken with `monitor_filters = esp32_exception_decoder`.
29 | **Any issue opened with a non readable stack trace will be ignored because not helpful at all.**
30 |
31 | As an alternative, you can use [https://maximeborges.github.io/esp-stacktrace-decoder/](https://maximeborges.github.io/esp-stacktrace-decoder/).
32 |
33 | **Additional notes**
34 |
35 | Add any other context about the problem here.
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Describe your question
4 | title: "[Q]"
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Set update schedule for GitHub Actions
2 |
3 | version: 2
4 | updates:
5 |
6 | - package-ecosystem: "github-actions"
7 | directory: "/"
8 | schedule:
9 | # Check for updates to GitHub Actions every week
10 | interval: "weekly"
11 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | daysUntilStale: 60
4 | daysUntilClose: 14
5 | limitPerRun: 30
6 | staleLabel: stale
7 | exemptLabels:
8 | - pinned
9 | - security
10 | - "to be implemented"
11 | - "for reference"
12 | - "move to PR"
13 | - "enhancement"
14 |
15 | only: issues
16 | onlyLabels: []
17 | exemptProjects: false
18 | exemptMilestones: false
19 | exemptAssignees: false
20 |
21 | markComment: >
22 | [STALE_SET] This issue has been automatically marked as stale because it has not had
23 | recent activity. It will be closed in 14 days if no further activity occurs. Thank you
24 | for your contributions.
25 |
26 | unmarkComment: >
27 | [STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it openin the future.
28 |
29 | closeComment: >
30 | [STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions.
31 |
32 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Async TCP CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 |
8 | concurrency:
9 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
10 | cancel-in-progress: true
11 |
12 | jobs:
13 | build-arduino:
14 | name: ${{ matrix.config }}
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | config: [arduino-cli.yaml, arduino-cli-dev.yaml]
20 | steps:
21 | - uses: actions/checkout@v4
22 | - uses: arduino/setup-arduino-cli@v1
23 | - name: Download board
24 | run: |
25 | arduino-cli --config-file ${{ matrix.config }} core update-index
26 | arduino-cli --config-file ${{ matrix.config }} board listall
27 | arduino-cli --config-file ${{ matrix.config }} core install esp32:esp32
28 | - name: Compile Sketch
29 | run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino
30 | - name: Compile Sketch with IPv6
31 | env:
32 | LWIP_IPV6: true
33 | run: arduino-cli --config-file ${{ matrix.config }} --library ./src/ compile --fqbn esp32:esp32:esp32 ./examples/Client/Client.ino
34 |
35 | platformio:
36 | name: "pio:${{ matrix.env }}:${{ matrix.board }}"
37 | runs-on: ubuntu-latest
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | include:
42 | - env: ci-arduino-2
43 | board: esp32dev
44 | - env: ci-arduino-2
45 | board: esp32-s2-saola-1
46 | - env: ci-arduino-2
47 | board: esp32-s3-devkitc-1
48 | - env: ci-arduino-2
49 | board: esp32-c3-devkitc-02
50 |
51 | - env: ci-arduino-3
52 | board: esp32dev
53 | - env: ci-arduino-3
54 | board: esp32-s2-saola-1
55 | - env: ci-arduino-3
56 | board: esp32-s3-devkitc-1
57 | - env: ci-arduino-3
58 | board: esp32-c3-devkitc-02
59 | - env: ci-arduino-3
60 | board: esp32-c6-devkitc-1
61 |
62 | - env: ci-arduino-311
63 | board: esp32dev
64 | - env: ci-arduino-311
65 | board: esp32-s2-saola-1
66 | - env: ci-arduino-311
67 | board: esp32-s3-devkitc-1
68 | - env: ci-arduino-311
69 | board: esp32-c3-devkitc-02
70 | - env: ci-arduino-311
71 | board: esp32-c6-devkitc-1
72 |
73 | steps:
74 | - name: Checkout
75 | uses: actions/checkout@v4
76 |
77 | - name: Cache PlatformIO
78 | uses: actions/cache@v4
79 | with:
80 | key: ${{ runner.os }}-pio
81 | path: |
82 | ~/.cache/pip
83 | ~/.platformio
84 |
85 | - name: Python
86 | uses: actions/setup-python@v5
87 | with:
88 | python-version: "3.x"
89 |
90 | - name: Build
91 | run: |
92 | python -m pip install --upgrade pip
93 | pip install --upgrade platformio
94 |
95 | - run: PLATFORMIO_SRC_DIR=examples/Client PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }}
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .lh
3 | /.pio
4 | /.vscode
5 |
6 | /logs
7 |
--------------------------------------------------------------------------------
/.gitpod.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-python-3.11
2 | USER gitpod
3 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - command: pip install --upgrade pip && pip install -U platformio && platformio run
3 |
4 | image:
5 | file: .gitpod.Dockerfile
6 |
7 | vscode:
8 | extensions:
9 | - shardulm94.trailing-spaces
10 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | set(COMPONENT_SRCDIRS
2 | "src"
3 | )
4 |
5 | set(COMPONENT_ADD_INCLUDEDIRS
6 | "src"
7 | )
8 |
9 | set(COMPONENT_REQUIRES
10 | "arduino-esp32"
11 | )
12 |
13 | register_component()
14 |
15 | target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)
16 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, religion, or sexual identity
11 | and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the
27 | overall community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or
32 | advances of any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email
36 | address, without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | https://sidweb.nl/cms3/en/contact.
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series
87 | of actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or
94 | permanent ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within
114 | the community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.0, available at
120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121 |
122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
123 | enforcement ladder](https://github.com/mozilla/diversity).
124 |
125 | [homepage]: https://www.contributor-covenant.org
126 |
127 | For answers to common questions about this code of conduct, see the FAQ at
128 | https://www.contributor-covenant.org/faq. Translations are available at
129 | https://www.contributor-covenant.org/translations.
130 |
--------------------------------------------------------------------------------
/Kconfig.projbuild:
--------------------------------------------------------------------------------
1 | menu "AsyncTCP Configuration"
2 |
3 | choice ASYNC_TCP_RUNNING_CORE
4 | bool "Core on which AsyncTCP's thread is running"
5 | default ASYNC_TCP_RUN_CORE1
6 | help
7 | Select on which core AsyncTCP is running
8 |
9 | config ASYNC_TCP_RUN_CORE0
10 | bool "CORE 0"
11 | config ASYNC_TCP_RUN_CORE1
12 | bool "CORE 1"
13 | config ASYNC_TCP_RUN_NO_AFFINITY
14 | bool "BOTH"
15 |
16 | endchoice
17 |
18 | config ASYNC_TCP_RUNNING_CORE
19 | int
20 | default 0 if ASYNC_TCP_RUN_CORE0
21 | default 1 if ASYNC_TCP_RUN_CORE1
22 | default -1 if ASYNC_TCP_RUN_NO_AFFINITY
23 |
24 | config ASYNC_TCP_USE_WDT
25 | bool "Enable WDT for the AsyncTCP task"
26 | default "y"
27 | help
28 | Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread.
29 |
30 | endmenu
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.ESP32Async.md:
--------------------------------------------------------------------------------
1 | # AsyncTCP
2 |
3 | [](https://opensource.org/license/lgpl-3-0/)
4 | [](https://github.com/ESP32Async/AsyncTCP/actions/workflows/ci.yml)
5 | [](https://registry.platformio.org/libraries/ESP32Async/AsyncTCP)
6 |
7 | Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY)
8 |
9 | ### Async TCP Library for ESP32 Arduino
10 |
11 | This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs.
12 |
13 | This library is the base for [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer)
14 |
15 | ## AsyncClient and AsyncServer
16 |
17 | The base classes on which everything else is built. They expose all possible scenarios, but are really raw and require more skills to use.
18 |
19 | ## Changes
20 |
21 | - `library.properties` for Arduino IDE users
22 | - Add `CONFIG_ASYNC_TCP_MAX_ACK_TIME`
23 | - Add `CONFIG_ASYNC_TCP_PRIORITY`
24 | - Add `CONFIG_ASYNC_TCP_QUEUE_SIZE`
25 | - Add `setKeepAlive()`
26 | - Arduino 3 / ESP-IDF 5 compatibility
27 | - Better CI
28 | - Better example
29 | - Customizable macros
30 | - 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
31 | - Fix for "ack timeout 4" client disconnects.
32 | - Fix from https://github.com/me-no-dev/AsyncTCP/pull/173 (partially applied)
33 | - Fix from https://github.com/me-no-dev/AsyncTCP/pull/184
34 | - IPv6
35 | - LIBRETINY support
36 | - LibreTuya
37 | - Reduce logging of non critical messages
38 | - Use IPADDR6_INIT() macro to set connecting IPv6 address
39 | - xTaskCreateUniversal function
40 |
41 | ## Coordinates
42 |
43 | ```
44 | ESP32Async/AsyncTCP @ ^3.3.2
45 | ```
46 |
47 | ## Important recommendations
48 |
49 | Most of the crashes are caused by improper configuration of the library for the project.
50 | Here are some recommendations to avoid them.
51 |
52 | I personally use the following configuration in my projects:
53 |
54 | ```c++
55 | -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
56 | -D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
57 | -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
58 | -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
59 | -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
60 | ```
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Project moved to [ESP32Async](https://github.com/organizations/ESP32Async) organization at [https://github.com/ESP32Async/AsyncTCP](https://github.com/ESP32Async/AsyncTCP)
4 |
5 | Discord Server: [https://discord.gg/X7zpGdyUcY](https://discord.gg/X7zpGdyUcY)
6 |
7 | Please see the new links:
8 |
9 | - `ESP32Async/ESPAsyncWebServer @ 3.6.0` (ESP32, ESP8266, RP2040)
10 | - `ESP32Async/AsyncTCP @ 3.3.2` (ESP32)
11 | - `ESP32Async/ESPAsyncTCP @ 2.0.0` (ESP8266)
12 | - `https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip` (AsyncTCP alternative for ESP32)
13 | - `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (RP2040)
14 |
--------------------------------------------------------------------------------
/arduino-cli-dev.yaml:
--------------------------------------------------------------------------------
1 | board_manager:
2 | additional_urls:
3 | - https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json
4 | directories:
5 | builtin.libraries: ./src/
6 | build_cache:
7 | compilations_before_purge: 10
8 | ttl: 720h0m0s
9 | daemon:
10 | port: "50051"
11 | library:
12 | enable_unsafe_install: false
13 | logging:
14 | file: ""
15 | format: text
16 | level: info
17 | metrics:
18 | addr: :9090
19 | enabled: true
20 | output:
21 | no_color: false
22 | sketch:
23 | always_export_binaries: false
24 | updater:
25 | enable_notification: true
26 |
--------------------------------------------------------------------------------
/arduino-cli.yaml:
--------------------------------------------------------------------------------
1 | board_manager:
2 | additional_urls:
3 | - https://espressif.github.io/arduino-esp32/package_esp32_index.json
4 | directories:
5 | builtin.libraries: ./src/
6 | build_cache:
7 | compilations_before_purge: 10
8 | ttl: 720h0m0s
9 | daemon:
10 | port: "50051"
11 | library:
12 | enable_unsafe_install: false
13 | logging:
14 | file: ""
15 | format: text
16 | level: info
17 | metrics:
18 | addr: :9090
19 | enabled: true
20 | output:
21 | no_color: false
22 | sketch:
23 | always_export_binaries: false
24 | updater:
25 | enable_notification: true
26 |
--------------------------------------------------------------------------------
/component.mk:
--------------------------------------------------------------------------------
1 | COMPONENT_ADD_INCLUDEDIRS := src
2 | COMPONENT_SRCDIRS := src
3 | CXXFLAGS += -fno-rtti
4 |
--------------------------------------------------------------------------------
/examples/Client/Client.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | // Run a server at the root of the project with:
6 | // > python3 -m http.server 3333
7 | // 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
8 | #define HOST "192.168.125.122"
9 | #define PORT 3333
10 |
11 | // WiFi SSID to connect to
12 | #define WIFI_SSID "IoT"
13 |
14 | // 16 slots on esp32 (CONFIG_LWIP_MAX_ACTIVE_TCP)
15 | #define MAX_CLIENTS CONFIG_LWIP_MAX_ACTIVE_TCP
16 | // #define MAX_CLIENTS 1
17 |
18 | size_t permits = MAX_CLIENTS;
19 |
20 | void makeRequest() {
21 | if (!permits)
22 | return;
23 |
24 | Serial.printf("** permits: %d\n", permits);
25 |
26 | AsyncClient* client = new AsyncClient;
27 |
28 | client->onError([](void* arg, AsyncClient* client, int8_t error) {
29 | Serial.printf("** error occurred %s \n", client->errorToString(error));
30 | client->close(true);
31 | delete client;
32 | });
33 |
34 | client->onConnect([](void* arg, AsyncClient* client) {
35 | permits--;
36 | Serial.printf("** client has been connected: %" PRIu16 "\n", client->localPort());
37 |
38 | client->onDisconnect([](void* arg, AsyncClient* client) {
39 | Serial.printf("** client has been disconnected: %" PRIu16 "\n", client->localPort());
40 | client->close(true);
41 | delete client;
42 |
43 | permits++;
44 | makeRequest();
45 | });
46 |
47 | client->onData([](void* arg, AsyncClient* client, void* data, size_t len) {
48 | Serial.printf("** data received by client: %" PRIu16 ": len=%u\n", client->localPort(), len);
49 | });
50 |
51 | client->write("GET /README.md HTTP/1.1\r\nHost: " HOST "\r\nUser-Agent: ESP\r\nConnection: close\r\n\r\n");
52 | });
53 |
54 | if (client->connect(HOST, PORT)) {
55 | } else {
56 | Serial.println("** connection failed");
57 | }
58 | }
59 |
60 | void setup() {
61 | Serial.begin(115200);
62 | while (!Serial)
63 | continue;
64 |
65 | WiFi.mode(WIFI_STA);
66 | WiFi.begin(WIFI_SSID);
67 | while (WiFi.status() != WL_CONNECTED) {
68 | delay(500);
69 | Serial.print(".");
70 | }
71 | Serial.println("** connected to WiFi");
72 | Serial.println(WiFi.localIP());
73 |
74 | for (size_t i = 0; i < MAX_CLIENTS; i++)
75 | makeRequest();
76 | }
77 |
78 | void loop() {
79 | delay(1000);
80 | Serial.printf("** free heap: %" PRIu32 "\n", ESP.getFreeHeap());
81 | }
82 |
--------------------------------------------------------------------------------
/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AsyncTCP",
3 | "version": "3.3.2",
4 | "description": "Asynchronous TCP Library for ESP32",
5 | "keywords": "async,tcp",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/ESP32Async/AsyncTCP.git"
9 | },
10 | "authors":
11 | {
12 | "name": "ESP32Async",
13 | "maintainer": true
14 | },
15 | "license": "LGPL-3.0",
16 | "frameworks": "arduino",
17 | "platforms": [
18 | "espressif32",
19 | "libretiny"
20 | ],
21 | "export": {
22 | "include": [
23 | "examples",
24 | "src",
25 | "library.json",
26 | "library.properties",
27 | "LICENSE",
28 | "README.md"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=Async TCP
2 | includes=AsyncTCP.h
3 | version=3.3.2
4 | author=ESP32Async
5 | maintainer=ESP32Async
6 | sentence=Async TCP Library for ESP32
7 | paragraph=Async TCP Library for ESP32
8 | category=Other
9 | url=https://github.com/ESP32Async/AsyncTCP.git
10 | architectures=*
11 | license=LGPL-3.0
12 |
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | [platformio]
2 | default_envs = arduino-2, arduino-3, arduino-311
3 | lib_dir = .
4 | src_dir = examples/Client
5 |
6 | [env]
7 | framework = arduino
8 | build_flags =
9 | -Wall -Wextra
10 | -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
11 | -D CONFIG_ASYNC_TCP_PRIORITY=10
12 | -D CONFIG_ASYNC_TCP_QUEUE_SIZE=64
13 | -D CONFIG_ASYNC_TCP_RUNNING_CORE=1
14 | -D CONFIG_ASYNC_TCP_STACK_SIZE=4096
15 | -D CONFIG_ARDUHAL_LOG_COLORS
16 | -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
17 | upload_protocol = esptool
18 | monitor_speed = 115200
19 | monitor_filters = esp32_exception_decoder, log2file
20 | board = esp32dev
21 |
22 | [env:arduino-2]
23 | platform = espressif32@6.9.0
24 |
25 | [env:arduino-3]
26 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
27 |
28 | [env:arduino-311]
29 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
30 |
31 | ; CI
32 |
33 | [env:ci-arduino-2]
34 | platform = espressif32@6.9.0
35 | board = ${sysenv.PIO_BOARD}
36 |
37 | [env:ci-arduino-3]
38 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
39 | board = ${sysenv.PIO_BOARD}
40 |
41 | [env:ci-arduino-311]
42 | platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
43 | board = ${sysenv.PIO_BOARD}
44 |
--------------------------------------------------------------------------------
/src/AsyncTCP.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Asynchronous TCP library for Espressif MCUs
3 |
4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved.
5 | This file is part of the esp8266 core for Arduino environment.
6 |
7 | This library is free software; you can redistribute it and/or
8 | modify it under the terms of the GNU Lesser General Public
9 | License as published by the Free Software Foundation; either
10 | version 2.1 of the License, or (at your option) any later version.
11 |
12 | This library is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | Lesser General Public License for more details.
16 |
17 | You should have received a copy of the GNU Lesser General Public
18 | License along with this library; if not, write to the Free Software
19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 | */
21 |
22 | #include "Arduino.h"
23 |
24 | #include "AsyncTCP.h"
25 |
26 | extern "C" {
27 | #include "lwip/dns.h"
28 | #include "lwip/err.h"
29 | #include "lwip/inet.h"
30 | #include "lwip/opt.h"
31 | #include "lwip/tcp.h"
32 | }
33 |
34 | #if CONFIG_ASYNC_TCP_USE_WDT
35 | #include "esp_task_wdt.h"
36 | #endif
37 |
38 | // Required for:
39 | // https://github.com/espressif/arduino-esp32/blob/3.0.3/libraries/Network/src/NetworkInterface.cpp#L37-L47
40 | #if ESP_IDF_VERSION_MAJOR >= 5
41 | #include
42 | #endif
43 |
44 | #define TAG "AsyncTCP"
45 |
46 | // https://github.com/espressif/arduino-esp32/issues/10526
47 | #ifdef CONFIG_LWIP_TCPIP_CORE_LOCKING
48 | #define TCP_MUTEX_LOCK() \
49 | if (!sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
50 | LOCK_TCPIP_CORE(); \
51 | }
52 |
53 | #define TCP_MUTEX_UNLOCK() \
54 | if (sys_thread_tcpip(LWIP_CORE_LOCK_QUERY_HOLDER)) { \
55 | UNLOCK_TCPIP_CORE(); \
56 | }
57 | #else // CONFIG_LWIP_TCPIP_CORE_LOCKING
58 | #define TCP_MUTEX_LOCK()
59 | #define TCP_MUTEX_UNLOCK()
60 | #endif // CONFIG_LWIP_TCPIP_CORE_LOCKING
61 |
62 | #define INVALID_CLOSED_SLOT -1
63 |
64 | /*
65 | TCP poll interval is specified in terms of the TCP coarse timer interval, which is called twice a second
66 | https://github.com/espressif/esp-lwip/blob/2acf959a2bb559313cd2bf9306c24612ba3d0e19/src/core/tcp.c#L1895
67 | */
68 | #define CONFIG_ASYNC_TCP_POLL_TIMER 1
69 |
70 | /*
71 | * TCP/IP Event Task
72 | * */
73 |
74 | typedef enum {
75 | LWIP_TCP_SENT,
76 | LWIP_TCP_RECV,
77 | LWIP_TCP_FIN,
78 | LWIP_TCP_ERROR,
79 | LWIP_TCP_POLL,
80 | LWIP_TCP_CLEAR,
81 | LWIP_TCP_ACCEPT,
82 | LWIP_TCP_CONNECTED,
83 | LWIP_TCP_DNS
84 | } lwip_event_t;
85 |
86 | typedef struct {
87 | lwip_event_t event;
88 | void* arg;
89 | union {
90 | struct {
91 | tcp_pcb* pcb;
92 | int8_t err;
93 | } connected;
94 | struct {
95 | int8_t err;
96 | } error;
97 | struct {
98 | tcp_pcb* pcb;
99 | uint16_t len;
100 | } sent;
101 | struct {
102 | tcp_pcb* pcb;
103 | pbuf* pb;
104 | int8_t err;
105 | } recv;
106 | struct {
107 | tcp_pcb* pcb;
108 | int8_t err;
109 | } fin;
110 | struct {
111 | tcp_pcb* pcb;
112 | } poll;
113 | struct {
114 | AsyncClient* client;
115 | } accept;
116 | struct {
117 | const char* name;
118 | ip_addr_t addr;
119 | } dns;
120 | };
121 | } lwip_event_packet_t;
122 |
123 | static QueueHandle_t _async_queue;
124 | static TaskHandle_t _async_service_task_handle = NULL;
125 |
126 | SemaphoreHandle_t _slots_lock;
127 | const int _number_of_closed_slots = CONFIG_LWIP_MAX_ACTIVE_TCP;
128 | static uint32_t _closed_slots[_number_of_closed_slots];
129 | static uint32_t _closed_index = []() {
130 | _slots_lock = xSemaphoreCreateBinary();
131 | xSemaphoreGive(_slots_lock);
132 | for (int i = 0; i < _number_of_closed_slots; ++i) {
133 | _closed_slots[i] = 1;
134 | }
135 | return 1;
136 | }();
137 |
138 | static inline bool _init_async_event_queue() {
139 | if (!_async_queue) {
140 | _async_queue = xQueueCreate(CONFIG_ASYNC_TCP_QUEUE_SIZE, sizeof(lwip_event_packet_t*));
141 | if (!_async_queue) {
142 | return false;
143 | }
144 | }
145 | return true;
146 | }
147 |
148 | static inline bool _send_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) {
149 | return _async_queue && xQueueSend(_async_queue, e, wait) == pdPASS;
150 | }
151 |
152 | static inline bool _prepend_async_event(lwip_event_packet_t** e, TickType_t wait = portMAX_DELAY) {
153 | return _async_queue && xQueueSendToFront(_async_queue, e, wait) == pdPASS;
154 | }
155 |
156 | static inline bool _get_async_event(lwip_event_packet_t** e) {
157 | if (!_async_queue) {
158 | return false;
159 | }
160 |
161 | #if CONFIG_ASYNC_TCP_USE_WDT
162 | // need to return periodically to feed the dog
163 | if (xQueueReceive(_async_queue, e, pdMS_TO_TICKS(1000)) != pdPASS)
164 | return false;
165 | #else
166 | if (xQueueReceive(_async_queue, e, portMAX_DELAY) != pdPASS)
167 | return false;
168 | #endif
169 |
170 | if ((*e)->event != LWIP_TCP_POLL)
171 | return true;
172 |
173 | /*
174 | Let's try to coalesce two (or more) consecutive poll events into one
175 | this usually happens with poor implemented user-callbacks that are runs too long and makes poll events to stack in the queue
176 | if consecutive user callback for a same connection runs longer that poll time then it will fill the queue with events until it deadlocks.
177 | This is a workaround to mitigate such poor designs and won't let other events/connections to starve the task time.
178 | It won't be effective if user would run multiple simultaneous long running callbacks due to message interleaving.
179 | todo: implement some kind of fair dequeing or (better) simply punish user for a bad designed callbacks by resetting hog connections
180 | */
181 | lwip_event_packet_t* next_pkt = NULL;
182 | while (xQueuePeek(_async_queue, &next_pkt, 0) == pdPASS) {
183 | if (next_pkt->arg == (*e)->arg && next_pkt->event == LWIP_TCP_POLL) {
184 | if (xQueueReceive(_async_queue, &next_pkt, 0) == pdPASS) {
185 | free(next_pkt);
186 | next_pkt = NULL;
187 | log_d("coalescing polls, network congestion or async callbacks might be too slow!");
188 | continue;
189 | }
190 | }
191 |
192 | // quit while loop if next event can't be discarded
193 | break;
194 | }
195 |
196 | /*
197 | now we have to decide if to proceed with poll callback handler or discard it?
198 | poor designed apps using asynctcp without proper dataflow control could flood the queue with interleaved pool/ack events.
199 | 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
200 | for long connections. Or poll callback could take long time starving other connections. Anyway our goal is to keep the queue length
201 | grows under control (if possible) and poll events are the safest to discard.
202 | Let's discard poll events processing using linear-increasing probability curve when queue size grows over 3/4
203 | Poll events are periodic and connection could get another chance next time
204 | */
205 | if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 4 + CONFIG_ASYNC_TCP_QUEUE_SIZE * 3 / 4)) {
206 | free(*e);
207 | *e = NULL;
208 | log_d("discarding poll due to queue congestion");
209 | // evict next event from a queue
210 | return _get_async_event(e);
211 | }
212 |
213 | // last resort return
214 | return true;
215 | }
216 |
217 | static bool _remove_events_with_arg(void* arg) {
218 | if (!_async_queue) {
219 | return false;
220 | }
221 |
222 | lwip_event_packet_t* first_packet = NULL;
223 | lwip_event_packet_t* packet = NULL;
224 |
225 | // figure out which is the first non-matching packet so we can keep the order
226 | while (!first_packet) {
227 | if (xQueueReceive(_async_queue, &first_packet, 0) != pdPASS) {
228 | return false;
229 | }
230 | // discard packet if matching
231 | if ((int)first_packet->arg == (int)arg) {
232 | free(first_packet);
233 | first_packet = NULL;
234 | } else if (xQueueSend(_async_queue, &first_packet, 0) != pdPASS) {
235 | // try to return first packet to the back of the queue
236 | // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue
237 | // otherwise it would deadlock, we have to discard the event
238 | free(first_packet);
239 | first_packet = NULL;
240 | return false;
241 | }
242 | }
243 |
244 | while (xQueuePeek(_async_queue, &packet, 0) == pdPASS && packet != first_packet) {
245 | if (xQueueReceive(_async_queue, &packet, 0) != pdPASS) {
246 | return false;
247 | }
248 | if ((int)packet->arg == (int)arg) {
249 | // remove matching event
250 | free(packet);
251 | packet = NULL;
252 | // otherwise try to requeue it
253 | } else if (xQueueSend(_async_queue, &packet, 0) != pdPASS) {
254 | // we can't wait here if queue is full, because this call has been done from the only consumer task of this queue
255 | // otherwise it would deadlock, we have to discard the event
256 | free(packet);
257 | packet = NULL;
258 | return false;
259 | }
260 | }
261 | return true;
262 | }
263 |
264 | static void _handle_async_event(lwip_event_packet_t* e) {
265 | if (e->arg == NULL) {
266 | // do nothing when arg is NULL
267 | // ets_printf("event arg == NULL: 0x%08x\n", e->recv.pcb);
268 | } else if (e->event == LWIP_TCP_CLEAR) {
269 | _remove_events_with_arg(e->arg);
270 | } else if (e->event == LWIP_TCP_RECV) {
271 | // ets_printf("-R: 0x%08x\n", e->recv.pcb);
272 | AsyncClient::_s_recv(e->arg, e->recv.pcb, e->recv.pb, e->recv.err);
273 | } else if (e->event == LWIP_TCP_FIN) {
274 | // ets_printf("-F: 0x%08x\n", e->fin.pcb);
275 | AsyncClient::_s_fin(e->arg, e->fin.pcb, e->fin.err);
276 | } else if (e->event == LWIP_TCP_SENT) {
277 | // ets_printf("-S: 0x%08x\n", e->sent.pcb);
278 | AsyncClient::_s_sent(e->arg, e->sent.pcb, e->sent.len);
279 | } else if (e->event == LWIP_TCP_POLL) {
280 | // ets_printf("-P: 0x%08x\n", e->poll.pcb);
281 | AsyncClient::_s_poll(e->arg, e->poll.pcb);
282 | } else if (e->event == LWIP_TCP_ERROR) {
283 | // ets_printf("-E: 0x%08x %d\n", e->arg, e->error.err);
284 | AsyncClient::_s_error(e->arg, e->error.err);
285 | } else if (e->event == LWIP_TCP_CONNECTED) {
286 | // ets_printf("C: 0x%08x 0x%08x %d\n", e->arg, e->connected.pcb, e->connected.err);
287 | AsyncClient::_s_connected(e->arg, e->connected.pcb, e->connected.err);
288 | } else if (e->event == LWIP_TCP_ACCEPT) {
289 | // ets_printf("A: 0x%08x 0x%08x\n", e->arg, e->accept.client);
290 | AsyncServer::_s_accepted(e->arg, e->accept.client);
291 | } else if (e->event == LWIP_TCP_DNS) {
292 | // ets_printf("D: 0x%08x %s = %s\n", e->arg, e->dns.name, ipaddr_ntoa(&e->dns.addr));
293 | AsyncClient::_s_dns_found(e->dns.name, &e->dns.addr, e->arg);
294 | }
295 | free((void*)(e));
296 | }
297 |
298 | static void _async_service_task(void* pvParameters) {
299 | #if CONFIG_ASYNC_TCP_USE_WDT
300 | if (esp_task_wdt_add(NULL) != ESP_OK) {
301 | log_w("Failed to add async task to WDT");
302 | }
303 | #endif
304 | lwip_event_packet_t* packet = NULL;
305 | for (;;) {
306 | if (_get_async_event(&packet)) {
307 | _handle_async_event(packet);
308 | }
309 | #if CONFIG_ASYNC_TCP_USE_WDT
310 | esp_task_wdt_reset();
311 | #endif
312 | }
313 | #if CONFIG_ASYNC_TCP_USE_WDT
314 | esp_task_wdt_delete(NULL);
315 | #endif
316 | vTaskDelete(NULL);
317 | _async_service_task_handle = NULL;
318 | }
319 | /*
320 | static void _stop_async_task(){
321 | if(_async_service_task_handle){
322 | vTaskDelete(_async_service_task_handle);
323 | _async_service_task_handle = NULL;
324 | }
325 | }
326 | */
327 |
328 | static bool customTaskCreateUniversal(
329 | TaskFunction_t pxTaskCode,
330 | const char* const pcName,
331 | const uint32_t usStackDepth,
332 | void* const pvParameters,
333 | UBaseType_t uxPriority,
334 | TaskHandle_t* const pxCreatedTask,
335 | const BaseType_t xCoreID) {
336 | #ifndef CONFIG_FREERTOS_UNICORE
337 | if (xCoreID >= 0 && xCoreID < 2) {
338 | return xTaskCreatePinnedToCore(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask, xCoreID);
339 | } else {
340 | #endif
341 | return xTaskCreate(pxTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedTask);
342 | #ifndef CONFIG_FREERTOS_UNICORE
343 | }
344 | #endif
345 | }
346 |
347 | static bool _start_async_task() {
348 | if (!_init_async_event_queue()) {
349 | return false;
350 | }
351 | if (!_async_service_task_handle) {
352 | 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);
353 | if (!_async_service_task_handle) {
354 | return false;
355 | }
356 | }
357 | return true;
358 | }
359 |
360 | /*
361 | * LwIP Callbacks
362 | * */
363 |
364 | static int8_t _tcp_clear_events(void* arg) {
365 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
366 | e->event = LWIP_TCP_CLEAR;
367 | e->arg = arg;
368 | if (!_prepend_async_event(&e)) {
369 | free((void*)(e));
370 | }
371 | return ERR_OK;
372 | }
373 |
374 | static int8_t _tcp_connected(void* arg, tcp_pcb* pcb, int8_t err) {
375 | // ets_printf("+C: 0x%08x\n", pcb);
376 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
377 | e->event = LWIP_TCP_CONNECTED;
378 | e->arg = arg;
379 | e->connected.pcb = pcb;
380 | e->connected.err = err;
381 | if (!_prepend_async_event(&e)) {
382 | free((void*)(e));
383 | }
384 | return ERR_OK;
385 | }
386 |
387 | static int8_t _tcp_poll(void* arg, struct tcp_pcb* pcb) {
388 | // throttle polling events queing when event queue is getting filled up, let it handle _onack's
389 | // log_d("qs:%u", uxQueueMessagesWaiting(_async_queue));
390 | if (uxQueueMessagesWaiting(_async_queue) > (rand() % CONFIG_ASYNC_TCP_QUEUE_SIZE / 2 + CONFIG_ASYNC_TCP_QUEUE_SIZE / 4)) {
391 | log_d("throttling");
392 | return ERR_OK;
393 | }
394 |
395 | // ets_printf("+P: 0x%08x\n", pcb);
396 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
397 | e->event = LWIP_TCP_POLL;
398 | e->arg = arg;
399 | e->poll.pcb = pcb;
400 | // poll events are not critical 'cause those are repetitive, so we may not wait the queue in any case
401 | if (!_send_async_event(&e, 0)) {
402 | free((void*)(e));
403 | }
404 | return ERR_OK;
405 | }
406 |
407 | static int8_t _tcp_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) {
408 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
409 | e->arg = arg;
410 | if (pb) {
411 | // ets_printf("+R: 0x%08x\n", pcb);
412 | e->event = LWIP_TCP_RECV;
413 | e->recv.pcb = pcb;
414 | e->recv.pb = pb;
415 | e->recv.err = err;
416 | } else {
417 | // ets_printf("+F: 0x%08x\n", pcb);
418 | e->event = LWIP_TCP_FIN;
419 | e->fin.pcb = pcb;
420 | e->fin.err = err;
421 | // close the PCB in LwIP thread
422 | AsyncClient::_s_lwip_fin(e->arg, e->fin.pcb, e->fin.err);
423 | }
424 | if (!_send_async_event(&e)) {
425 | free((void*)(e));
426 | }
427 | return ERR_OK;
428 | }
429 |
430 | static int8_t _tcp_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) {
431 | // ets_printf("+S: 0x%08x\n", pcb);
432 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
433 | e->event = LWIP_TCP_SENT;
434 | e->arg = arg;
435 | e->sent.pcb = pcb;
436 | e->sent.len = len;
437 | if (!_send_async_event(&e)) {
438 | free((void*)(e));
439 | }
440 | return ERR_OK;
441 | }
442 |
443 | static void _tcp_error(void* arg, int8_t err) {
444 | // ets_printf("+E: 0x%08x\n", arg);
445 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
446 | e->event = LWIP_TCP_ERROR;
447 | e->arg = arg;
448 | e->error.err = err;
449 | if (!_send_async_event(&e)) {
450 | free((void*)(e));
451 | }
452 | }
453 |
454 | static void _tcp_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) {
455 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
456 | // ets_printf("+DNS: name=%s ipaddr=0x%08x arg=%x\n", name, ipaddr, arg);
457 | e->event = LWIP_TCP_DNS;
458 | e->arg = arg;
459 | e->dns.name = name;
460 | if (ipaddr) {
461 | memcpy(&e->dns.addr, ipaddr, sizeof(struct ip_addr));
462 | } else {
463 | memset(&e->dns.addr, 0, sizeof(e->dns.addr));
464 | }
465 | if (!_send_async_event(&e)) {
466 | free((void*)(e));
467 | }
468 | }
469 |
470 | // Used to switch out from LwIP thread
471 | static int8_t _tcp_accept(void* arg, AsyncClient* client) {
472 | lwip_event_packet_t* e = (lwip_event_packet_t*)malloc(sizeof(lwip_event_packet_t));
473 | e->event = LWIP_TCP_ACCEPT;
474 | e->arg = arg;
475 | e->accept.client = client;
476 | if (!_prepend_async_event(&e)) {
477 | free((void*)(e));
478 | }
479 | return ERR_OK;
480 | }
481 |
482 | /*
483 | * TCP/IP API Calls
484 | * */
485 |
486 | #include "lwip/priv/tcpip_priv.h"
487 |
488 | typedef struct {
489 | struct tcpip_api_call_data call;
490 | tcp_pcb* pcb;
491 | int8_t closed_slot;
492 | int8_t err;
493 | union {
494 | struct {
495 | const char* data;
496 | size_t size;
497 | uint8_t apiflags;
498 | } write;
499 | size_t received;
500 | struct {
501 | ip_addr_t* addr;
502 | uint16_t port;
503 | tcp_connected_fn cb;
504 | } connect;
505 | struct {
506 | ip_addr_t* addr;
507 | uint16_t port;
508 | } bind;
509 | uint8_t backlog;
510 | };
511 | } tcp_api_call_t;
512 |
513 | static err_t _tcp_output_api(struct tcpip_api_call_data* api_call_msg) {
514 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
515 | msg->err = ERR_CONN;
516 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) {
517 | msg->err = tcp_output(msg->pcb);
518 | }
519 | return msg->err;
520 | }
521 |
522 | static esp_err_t _tcp_output(tcp_pcb* pcb, int8_t closed_slot) {
523 | if (!pcb) {
524 | return ERR_CONN;
525 | }
526 | tcp_api_call_t msg;
527 | msg.pcb = pcb;
528 | msg.closed_slot = closed_slot;
529 | tcpip_api_call(_tcp_output_api, (struct tcpip_api_call_data*)&msg);
530 | return msg.err;
531 | }
532 |
533 | static err_t _tcp_write_api(struct tcpip_api_call_data* api_call_msg) {
534 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
535 | msg->err = ERR_CONN;
536 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) {
537 | msg->err = tcp_write(msg->pcb, msg->write.data, msg->write.size, msg->write.apiflags);
538 | }
539 | return msg->err;
540 | }
541 |
542 | static esp_err_t _tcp_write(tcp_pcb* pcb, int8_t closed_slot, const char* data, size_t size, uint8_t apiflags) {
543 | if (!pcb) {
544 | return ERR_CONN;
545 | }
546 | tcp_api_call_t msg;
547 | msg.pcb = pcb;
548 | msg.closed_slot = closed_slot;
549 | msg.write.data = data;
550 | msg.write.size = size;
551 | msg.write.apiflags = apiflags;
552 | tcpip_api_call(_tcp_write_api, (struct tcpip_api_call_data*)&msg);
553 | return msg.err;
554 | }
555 |
556 | static err_t _tcp_recved_api(struct tcpip_api_call_data* api_call_msg) {
557 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
558 | msg->err = ERR_CONN;
559 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) {
560 | // if(msg->closed_slot != INVALID_CLOSED_SLOT && !_closed_slots[msg->closed_slot]) {
561 | // if(msg->closed_slot != INVALID_CLOSED_SLOT) {
562 | msg->err = 0;
563 | tcp_recved(msg->pcb, msg->received);
564 | }
565 | return msg->err;
566 | }
567 |
568 | static esp_err_t _tcp_recved(tcp_pcb* pcb, int8_t closed_slot, size_t len) {
569 | if (!pcb) {
570 | return ERR_CONN;
571 | }
572 | tcp_api_call_t msg;
573 | msg.pcb = pcb;
574 | msg.closed_slot = closed_slot;
575 | msg.received = len;
576 | tcpip_api_call(_tcp_recved_api, (struct tcpip_api_call_data*)&msg);
577 | return msg.err;
578 | }
579 |
580 | static err_t _tcp_close_api(struct tcpip_api_call_data* api_call_msg) {
581 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
582 | msg->err = ERR_CONN;
583 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) {
584 | msg->err = tcp_close(msg->pcb);
585 | }
586 | return msg->err;
587 | }
588 |
589 | static esp_err_t _tcp_close(tcp_pcb* pcb, int8_t closed_slot) {
590 | if (!pcb) {
591 | return ERR_CONN;
592 | }
593 | tcp_api_call_t msg;
594 | msg.pcb = pcb;
595 | msg.closed_slot = closed_slot;
596 | tcpip_api_call(_tcp_close_api, (struct tcpip_api_call_data*)&msg);
597 | return msg.err;
598 | }
599 |
600 | static err_t _tcp_abort_api(struct tcpip_api_call_data* api_call_msg) {
601 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
602 | msg->err = ERR_CONN;
603 | if (msg->closed_slot == INVALID_CLOSED_SLOT || !_closed_slots[msg->closed_slot]) {
604 | tcp_abort(msg->pcb);
605 | }
606 | return msg->err;
607 | }
608 |
609 | static esp_err_t _tcp_abort(tcp_pcb* pcb, int8_t closed_slot) {
610 | if (!pcb) {
611 | return ERR_CONN;
612 | }
613 | tcp_api_call_t msg;
614 | msg.pcb = pcb;
615 | msg.closed_slot = closed_slot;
616 | tcpip_api_call(_tcp_abort_api, (struct tcpip_api_call_data*)&msg);
617 | return msg.err;
618 | }
619 |
620 | static err_t _tcp_connect_api(struct tcpip_api_call_data* api_call_msg) {
621 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
622 | msg->err = tcp_connect(msg->pcb, msg->connect.addr, msg->connect.port, msg->connect.cb);
623 | return msg->err;
624 | }
625 |
626 | static esp_err_t _tcp_connect(tcp_pcb* pcb, int8_t closed_slot, ip_addr_t* addr, uint16_t port, tcp_connected_fn cb) {
627 | if (!pcb) {
628 | return ESP_FAIL;
629 | }
630 | tcp_api_call_t msg;
631 | msg.pcb = pcb;
632 | msg.closed_slot = closed_slot;
633 | msg.connect.addr = addr;
634 | msg.connect.port = port;
635 | msg.connect.cb = cb;
636 | tcpip_api_call(_tcp_connect_api, (struct tcpip_api_call_data*)&msg);
637 | return msg.err;
638 | }
639 |
640 | static err_t _tcp_bind_api(struct tcpip_api_call_data* api_call_msg) {
641 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
642 | msg->err = tcp_bind(msg->pcb, msg->bind.addr, msg->bind.port);
643 | return msg->err;
644 | }
645 |
646 | static esp_err_t _tcp_bind(tcp_pcb* pcb, ip_addr_t* addr, uint16_t port) {
647 | if (!pcb) {
648 | return ESP_FAIL;
649 | }
650 | tcp_api_call_t msg;
651 | msg.pcb = pcb;
652 | msg.closed_slot = -1;
653 | msg.bind.addr = addr;
654 | msg.bind.port = port;
655 | tcpip_api_call(_tcp_bind_api, (struct tcpip_api_call_data*)&msg);
656 | return msg.err;
657 | }
658 |
659 | static err_t _tcp_listen_api(struct tcpip_api_call_data* api_call_msg) {
660 | tcp_api_call_t* msg = (tcp_api_call_t*)api_call_msg;
661 | msg->err = 0;
662 | msg->pcb = tcp_listen_with_backlog(msg->pcb, msg->backlog);
663 | return msg->err;
664 | }
665 |
666 | static tcp_pcb* _tcp_listen_with_backlog(tcp_pcb* pcb, uint8_t backlog) {
667 | if (!pcb) {
668 | return NULL;
669 | }
670 | tcp_api_call_t msg;
671 | msg.pcb = pcb;
672 | msg.closed_slot = -1;
673 | msg.backlog = backlog ? backlog : 0xFF;
674 | tcpip_api_call(_tcp_listen_api, (struct tcpip_api_call_data*)&msg);
675 | return msg.pcb;
676 | }
677 |
678 | /*
679 | Async TCP Client
680 | */
681 |
682 | AsyncClient::AsyncClient(tcp_pcb* pcb)
683 | : _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) {
684 | _pcb = pcb;
685 | _closed_slot = INVALID_CLOSED_SLOT;
686 | if (_pcb) {
687 | _rx_last_packet = millis();
688 | tcp_arg(_pcb, this);
689 | tcp_recv(_pcb, &_tcp_recv);
690 | tcp_sent(_pcb, &_tcp_sent);
691 | tcp_err(_pcb, &_tcp_error);
692 | tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER);
693 | if (!_allocate_closed_slot()) {
694 | _close();
695 | }
696 | }
697 | }
698 |
699 | AsyncClient::~AsyncClient() {
700 | if (_pcb) {
701 | _close();
702 | }
703 | _free_closed_slot();
704 | }
705 |
706 | /*
707 | * Operators
708 | * */
709 |
710 | AsyncClient& AsyncClient::operator=(const AsyncClient& other) {
711 | if (_pcb) {
712 | _close();
713 | }
714 |
715 | _pcb = other._pcb;
716 | _closed_slot = other._closed_slot;
717 | if (_pcb) {
718 | _rx_last_packet = millis();
719 | tcp_arg(_pcb, this);
720 | tcp_recv(_pcb, &_tcp_recv);
721 | tcp_sent(_pcb, &_tcp_sent);
722 | tcp_err(_pcb, &_tcp_error);
723 | tcp_poll(_pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER);
724 | }
725 | return *this;
726 | }
727 |
728 | bool AsyncClient::operator==(const AsyncClient& other) {
729 | return _pcb == other._pcb;
730 | }
731 |
732 | AsyncClient& AsyncClient::operator+=(const AsyncClient& other) {
733 | if (next == NULL) {
734 | next = (AsyncClient*)(&other);
735 | next->prev = this;
736 | } else {
737 | AsyncClient* c = next;
738 | while (c->next != NULL) {
739 | c = c->next;
740 | }
741 | c->next = (AsyncClient*)(&other);
742 | c->next->prev = c;
743 | }
744 | return *this;
745 | }
746 |
747 | /*
748 | * Callback Setters
749 | * */
750 |
751 | void AsyncClient::onConnect(AcConnectHandler cb, void* arg) {
752 | _connect_cb = cb;
753 | _connect_cb_arg = arg;
754 | }
755 |
756 | void AsyncClient::onDisconnect(AcConnectHandler cb, void* arg) {
757 | _discard_cb = cb;
758 | _discard_cb_arg = arg;
759 | }
760 |
761 | void AsyncClient::onAck(AcAckHandler cb, void* arg) {
762 | _sent_cb = cb;
763 | _sent_cb_arg = arg;
764 | }
765 |
766 | void AsyncClient::onError(AcErrorHandler cb, void* arg) {
767 | _error_cb = cb;
768 | _error_cb_arg = arg;
769 | }
770 |
771 | void AsyncClient::onData(AcDataHandler cb, void* arg) {
772 | _recv_cb = cb;
773 | _recv_cb_arg = arg;
774 | }
775 |
776 | void AsyncClient::onPacket(AcPacketHandler cb, void* arg) {
777 | _pb_cb = cb;
778 | _pb_cb_arg = arg;
779 | }
780 |
781 | void AsyncClient::onTimeout(AcTimeoutHandler cb, void* arg) {
782 | _timeout_cb = cb;
783 | _timeout_cb_arg = arg;
784 | }
785 |
786 | void AsyncClient::onPoll(AcConnectHandler cb, void* arg) {
787 | _poll_cb = cb;
788 | _poll_cb_arg = arg;
789 | }
790 |
791 | /*
792 | * Main Public Methods
793 | * */
794 |
795 | bool AsyncClient::_connect(ip_addr_t addr, uint16_t port) {
796 | if (_pcb) {
797 | log_d("already connected, state %d", _pcb->state);
798 | return false;
799 | }
800 | if (!_start_async_task()) {
801 | log_e("failed to start task");
802 | return false;
803 | }
804 |
805 | if (!_allocate_closed_slot()) {
806 | log_e("failed to allocate: closed slot full");
807 | return false;
808 | }
809 |
810 | TCP_MUTEX_LOCK();
811 | tcp_pcb* pcb = tcp_new_ip_type(addr.type);
812 | if (!pcb) {
813 | TCP_MUTEX_UNLOCK();
814 | log_e("pcb == NULL");
815 | return false;
816 | }
817 | tcp_arg(pcb, this);
818 | tcp_err(pcb, &_tcp_error);
819 | tcp_recv(pcb, &_tcp_recv);
820 | tcp_sent(pcb, &_tcp_sent);
821 | tcp_poll(pcb, &_tcp_poll, CONFIG_ASYNC_TCP_POLL_TIMER);
822 | TCP_MUTEX_UNLOCK();
823 |
824 | esp_err_t err = _tcp_connect(pcb, _closed_slot, &addr, port, (tcp_connected_fn)&_tcp_connected);
825 | return err == ESP_OK;
826 | }
827 |
828 | bool AsyncClient::connect(const IPAddress& ip, uint16_t port) {
829 | ip_addr_t addr;
830 | #if ESP_IDF_VERSION_MAJOR < 5
831 | addr.u_addr.ip4.addr = ip;
832 | addr.type = IPADDR_TYPE_V4;
833 | #else
834 | ip.to_ip_addr_t(&addr);
835 | #endif
836 |
837 | return _connect(addr, port);
838 | }
839 |
840 | #if LWIP_IPV6 && ESP_IDF_VERSION_MAJOR < 5
841 | bool AsyncClient::connect(const IPv6Address& ip, uint16_t port) {
842 | auto ipaddr = static_cast(ip);
843 | ip_addr_t addr = IPADDR6_INIT(ipaddr[0], ipaddr[1], ipaddr[2], ipaddr[3]);
844 |
845 | return _connect(addr, port);
846 | }
847 | #endif
848 |
849 | bool AsyncClient::connect(const char* host, uint16_t port) {
850 | ip_addr_t addr;
851 |
852 | if (!_start_async_task()) {
853 | log_e("failed to start task");
854 | return false;
855 | }
856 |
857 | TCP_MUTEX_LOCK();
858 | err_t err = dns_gethostbyname(host, &addr, (dns_found_callback)&_tcp_dns_found, this);
859 | TCP_MUTEX_UNLOCK();
860 | if (err == ERR_OK) {
861 | #if ESP_IDF_VERSION_MAJOR < 5
862 | #if LWIP_IPV6
863 | if (addr.type == IPADDR_TYPE_V6) {
864 | return connect(IPv6Address(addr.u_addr.ip6.addr), port);
865 | }
866 | return connect(IPAddress(addr.u_addr.ip4.addr), port);
867 | #else
868 | return connect(IPAddress(addr.addr), port);
869 | #endif
870 | #else
871 | return _connect(addr, port);
872 | #endif
873 | } else if (err == ERR_INPROGRESS) {
874 | _connect_port = port;
875 | return true;
876 | }
877 | log_d("error: %d", err);
878 | return false;
879 | }
880 |
881 | void AsyncClient::close(bool now) {
882 | if (_pcb) {
883 | _tcp_recved(_pcb, _closed_slot, _rx_ack_len);
884 | }
885 | _close();
886 | }
887 |
888 | int8_t AsyncClient::abort() {
889 | if (_pcb) {
890 | _tcp_abort(_pcb, _closed_slot);
891 | _pcb = NULL;
892 | }
893 | return ERR_ABRT;
894 | }
895 |
896 | size_t AsyncClient::space() {
897 | if ((_pcb != NULL) && (_pcb->state == ESTABLISHED)) {
898 | return tcp_sndbuf(_pcb);
899 | }
900 | return 0;
901 | }
902 |
903 | size_t AsyncClient::add(const char* data, size_t size, uint8_t apiflags) {
904 | if (!_pcb || size == 0 || data == NULL) {
905 | return 0;
906 | }
907 | size_t room = space();
908 | if (!room) {
909 | return 0;
910 | }
911 | size_t will_send = (room < size) ? room : size;
912 | int8_t err = ERR_OK;
913 | err = _tcp_write(_pcb, _closed_slot, data, will_send, apiflags);
914 | if (err != ERR_OK) {
915 | return 0;
916 | }
917 | return will_send;
918 | }
919 |
920 | bool AsyncClient::send() {
921 | auto backup = _tx_last_packet;
922 | _tx_last_packet = millis();
923 | if (_tcp_output(_pcb, _closed_slot) == ERR_OK) {
924 | return true;
925 | }
926 | _tx_last_packet = backup;
927 | return false;
928 | }
929 |
930 | size_t AsyncClient::ack(size_t len) {
931 | if (len > _rx_ack_len)
932 | len = _rx_ack_len;
933 | if (len) {
934 | _tcp_recved(_pcb, _closed_slot, len);
935 | }
936 | _rx_ack_len -= len;
937 | return len;
938 | }
939 |
940 | void AsyncClient::ackPacket(struct pbuf* pb) {
941 | if (!pb) {
942 | return;
943 | }
944 | _tcp_recved(_pcb, _closed_slot, pb->len);
945 | pbuf_free(pb);
946 | }
947 |
948 | /*
949 | * Main Private Methods
950 | * */
951 |
952 | int8_t AsyncClient::_close() {
953 | // ets_printf("X: 0x%08x\n", (uint32_t)this);
954 | int8_t err = ERR_OK;
955 | if (_pcb) {
956 | TCP_MUTEX_LOCK();
957 | tcp_arg(_pcb, NULL);
958 | tcp_sent(_pcb, NULL);
959 | tcp_recv(_pcb, NULL);
960 | tcp_err(_pcb, NULL);
961 | tcp_poll(_pcb, NULL, 0);
962 | TCP_MUTEX_UNLOCK();
963 | _tcp_clear_events(this);
964 | err = _tcp_close(_pcb, _closed_slot);
965 | if (err != ERR_OK) {
966 | err = abort();
967 | }
968 | _free_closed_slot();
969 | _pcb = NULL;
970 | if (_discard_cb) {
971 | _discard_cb(_discard_cb_arg, this);
972 | }
973 | }
974 | return err;
975 | }
976 |
977 | bool AsyncClient::_allocate_closed_slot() {
978 | if (_closed_slot != INVALID_CLOSED_SLOT) {
979 | return true;
980 | }
981 | xSemaphoreTake(_slots_lock, portMAX_DELAY);
982 | uint32_t closed_slot_min_index = 0;
983 | for (int i = 0; i < _number_of_closed_slots; ++i) {
984 | if ((_closed_slot == INVALID_CLOSED_SLOT || _closed_slots[i] <= closed_slot_min_index) && _closed_slots[i] != 0) {
985 | closed_slot_min_index = _closed_slots[i];
986 | _closed_slot = i;
987 | }
988 | }
989 | if (_closed_slot != INVALID_CLOSED_SLOT) {
990 | _closed_slots[_closed_slot] = 0;
991 | }
992 | xSemaphoreGive(_slots_lock);
993 | return (_closed_slot != INVALID_CLOSED_SLOT);
994 | }
995 |
996 | void AsyncClient::_free_closed_slot() {
997 | xSemaphoreTake(_slots_lock, portMAX_DELAY);
998 | if (_closed_slot != INVALID_CLOSED_SLOT) {
999 | _closed_slots[_closed_slot] = _closed_index;
1000 | _closed_slot = INVALID_CLOSED_SLOT;
1001 | ++_closed_index;
1002 | }
1003 | xSemaphoreGive(_slots_lock);
1004 | }
1005 |
1006 | /*
1007 | * Private Callbacks
1008 | * */
1009 |
1010 | int8_t AsyncClient::_connected(tcp_pcb* pcb, int8_t err) {
1011 | _pcb = reinterpret_cast(pcb);
1012 | if (_pcb) {
1013 | _rx_last_packet = millis();
1014 | }
1015 | if (_connect_cb) {
1016 | _connect_cb(_connect_cb_arg, this);
1017 | }
1018 | return ERR_OK;
1019 | }
1020 |
1021 | void AsyncClient::_error(int8_t err) {
1022 | if (_pcb) {
1023 | TCP_MUTEX_LOCK();
1024 | tcp_arg(_pcb, NULL);
1025 | if (_pcb->state == LISTEN) {
1026 | tcp_sent(_pcb, NULL);
1027 | tcp_recv(_pcb, NULL);
1028 | tcp_err(_pcb, NULL);
1029 | tcp_poll(_pcb, NULL, 0);
1030 | }
1031 | TCP_MUTEX_UNLOCK();
1032 | _free_closed_slot();
1033 | _pcb = NULL;
1034 | }
1035 | if (_error_cb) {
1036 | _error_cb(_error_cb_arg, this, err);
1037 | }
1038 | if (_discard_cb) {
1039 | _discard_cb(_discard_cb_arg, this);
1040 | }
1041 | }
1042 |
1043 | // In LwIP Thread
1044 | int8_t AsyncClient::_lwip_fin(tcp_pcb* pcb, int8_t err) {
1045 | if (!_pcb || pcb != _pcb) {
1046 | log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb);
1047 | return ERR_OK;
1048 | }
1049 | tcp_arg(_pcb, NULL);
1050 | if (_pcb->state == LISTEN) {
1051 | tcp_sent(_pcb, NULL);
1052 | tcp_recv(_pcb, NULL);
1053 | tcp_err(_pcb, NULL);
1054 | tcp_poll(_pcb, NULL, 0);
1055 | }
1056 | if (tcp_close(_pcb) != ERR_OK) {
1057 | tcp_abort(_pcb);
1058 | }
1059 | _free_closed_slot();
1060 | _pcb = NULL;
1061 | return ERR_OK;
1062 | }
1063 |
1064 | // In Async Thread
1065 | int8_t AsyncClient::_fin(tcp_pcb* pcb, int8_t err) {
1066 | _tcp_clear_events(this);
1067 | if (_discard_cb) {
1068 | _discard_cb(_discard_cb_arg, this);
1069 | }
1070 | return ERR_OK;
1071 | }
1072 |
1073 | int8_t AsyncClient::_sent(tcp_pcb* pcb, uint16_t len) {
1074 | _rx_last_ack = _rx_last_packet = millis();
1075 | if (_sent_cb) {
1076 | _sent_cb(_sent_cb_arg, this, len, (_rx_last_packet - _tx_last_packet));
1077 | }
1078 | return ERR_OK;
1079 | }
1080 |
1081 | int8_t AsyncClient::_recv(tcp_pcb* pcb, pbuf* pb, int8_t err) {
1082 | while (pb != NULL) {
1083 | _rx_last_packet = millis();
1084 | // we should not ack before we assimilate the data
1085 | _ack_pcb = true;
1086 | pbuf* b = pb;
1087 | pb = b->next;
1088 | b->next = NULL;
1089 | if (_pb_cb) {
1090 | _pb_cb(_pb_cb_arg, this, b);
1091 | } else {
1092 | if (_recv_cb) {
1093 | _recv_cb(_recv_cb_arg, this, b->payload, b->len);
1094 | }
1095 | if (!_ack_pcb) {
1096 | _rx_ack_len += b->len;
1097 | } else if (_pcb) {
1098 | _tcp_recved(_pcb, _closed_slot, b->len);
1099 | }
1100 | }
1101 | pbuf_free(b);
1102 | }
1103 | return ERR_OK;
1104 | }
1105 |
1106 | int8_t AsyncClient::_poll(tcp_pcb* pcb) {
1107 | if (!_pcb) {
1108 | // log_d("pcb is NULL");
1109 | return ERR_OK;
1110 | }
1111 | if (pcb != _pcb) {
1112 | log_d("0x%08x != 0x%08x", (uint32_t)pcb, (uint32_t)_pcb);
1113 | return ERR_OK;
1114 | }
1115 |
1116 | uint32_t now = millis();
1117 |
1118 | // ACK Timeout
1119 | if (_ack_timeout) {
1120 | const uint32_t one_day = 86400000;
1121 | bool last_tx_is_after_last_ack = (_rx_last_ack - _tx_last_packet + one_day) < one_day;
1122 | if (last_tx_is_after_last_ack && (now - _tx_last_packet) >= _ack_timeout) {
1123 | log_d("ack timeout %d", pcb->state);
1124 | if (_timeout_cb)
1125 | _timeout_cb(_timeout_cb_arg, this, (now - _tx_last_packet));
1126 | return ERR_OK;
1127 | }
1128 | }
1129 | // RX Timeout
1130 | if (_rx_timeout && (now - _rx_last_packet) >= (_rx_timeout * 1000)) {
1131 | log_d("rx timeout %d", pcb->state);
1132 | _close();
1133 | return ERR_OK;
1134 | }
1135 | // Everything is fine
1136 | if (_poll_cb) {
1137 | _poll_cb(_poll_cb_arg, this);
1138 | }
1139 | return ERR_OK;
1140 | }
1141 |
1142 | void AsyncClient::_dns_found(struct ip_addr* ipaddr) {
1143 | #if ESP_IDF_VERSION_MAJOR < 5
1144 | if (ipaddr && IP_IS_V4(ipaddr)) {
1145 | connect(IPAddress(ip_addr_get_ip4_u32(ipaddr)), _connect_port);
1146 | #if LWIP_IPV6
1147 | } else if (ipaddr && ipaddr->u_addr.ip6.addr) {
1148 | connect(IPv6Address(ipaddr->u_addr.ip6.addr), _connect_port);
1149 | #endif
1150 | #else
1151 | if (ipaddr) {
1152 | IPAddress ip;
1153 | ip.from_ip_addr_t(ipaddr);
1154 | connect(ip, _connect_port);
1155 | #endif
1156 | } else {
1157 | if (_error_cb) {
1158 | _error_cb(_error_cb_arg, this, -55);
1159 | }
1160 | if (_discard_cb) {
1161 | _discard_cb(_discard_cb_arg, this);
1162 | }
1163 | }
1164 | }
1165 |
1166 | /*
1167 | * Public Helper Methods
1168 | * */
1169 |
1170 | bool AsyncClient::free() {
1171 | if (!_pcb) {
1172 | return true;
1173 | }
1174 | if (_pcb->state == CLOSED || _pcb->state > ESTABLISHED) {
1175 | return true;
1176 | }
1177 | return false;
1178 | }
1179 |
1180 | size_t AsyncClient::write(const char* data, size_t size, uint8_t apiflags) {
1181 | size_t will_send = add(data, size, apiflags);
1182 | if (!will_send || !send()) {
1183 | return 0;
1184 | }
1185 | return will_send;
1186 | }
1187 |
1188 | void AsyncClient::setRxTimeout(uint32_t timeout) {
1189 | _rx_timeout = timeout;
1190 | }
1191 |
1192 | uint32_t AsyncClient::getRxTimeout() {
1193 | return _rx_timeout;
1194 | }
1195 |
1196 | uint32_t AsyncClient::getAckTimeout() {
1197 | return _ack_timeout;
1198 | }
1199 |
1200 | void AsyncClient::setAckTimeout(uint32_t timeout) {
1201 | _ack_timeout = timeout;
1202 | }
1203 |
1204 | void AsyncClient::setNoDelay(bool nodelay) {
1205 | if (!_pcb) {
1206 | return;
1207 | }
1208 | if (nodelay) {
1209 | tcp_nagle_disable(_pcb);
1210 | } else {
1211 | tcp_nagle_enable(_pcb);
1212 | }
1213 | }
1214 |
1215 | bool AsyncClient::getNoDelay() {
1216 | if (!_pcb) {
1217 | return false;
1218 | }
1219 | return tcp_nagle_disabled(_pcb);
1220 | }
1221 |
1222 | void AsyncClient::setKeepAlive(uint32_t ms, uint8_t cnt) {
1223 | if (ms != 0) {
1224 | _pcb->so_options |= SOF_KEEPALIVE; // Turn on TCP Keepalive for the given pcb
1225 | // Set the time between keepalive messages in milli-seconds
1226 | _pcb->keep_idle = ms;
1227 | _pcb->keep_intvl = ms;
1228 | _pcb->keep_cnt = cnt; // The number of unanswered probes required to force closure of the socket
1229 | } else {
1230 | _pcb->so_options &= ~SOF_KEEPALIVE; // Turn off TCP Keepalive for the given pcb
1231 | }
1232 | }
1233 |
1234 | uint16_t AsyncClient::getMss() {
1235 | if (!_pcb) {
1236 | return 0;
1237 | }
1238 | return tcp_mss(_pcb);
1239 | }
1240 |
1241 | uint32_t AsyncClient::getRemoteAddress() {
1242 | if (!_pcb) {
1243 | return 0;
1244 | }
1245 | #if LWIP_IPV4 && LWIP_IPV6
1246 | return _pcb->remote_ip.u_addr.ip4.addr;
1247 | #else
1248 | return _pcb->remote_ip.addr;
1249 | #endif
1250 | }
1251 |
1252 | #if LWIP_IPV6
1253 | ip6_addr_t AsyncClient::getRemoteAddress6() {
1254 | if (!_pcb) {
1255 | ip6_addr_t nulladdr;
1256 | ip6_addr_set_zero(&nulladdr);
1257 | return nulladdr;
1258 | }
1259 | return _pcb->remote_ip.u_addr.ip6;
1260 | }
1261 |
1262 | ip6_addr_t AsyncClient::getLocalAddress6() {
1263 | if (!_pcb) {
1264 | ip6_addr_t nulladdr;
1265 | ip6_addr_set_zero(&nulladdr);
1266 | return nulladdr;
1267 | }
1268 | return _pcb->local_ip.u_addr.ip6;
1269 | }
1270 | #if ESP_IDF_VERSION_MAJOR < 5
1271 | IPv6Address AsyncClient::remoteIP6() {
1272 | return IPv6Address(getRemoteAddress6().addr);
1273 | }
1274 |
1275 | IPv6Address AsyncClient::localIP6() {
1276 | return IPv6Address(getLocalAddress6().addr);
1277 | }
1278 | #else
1279 | IPAddress AsyncClient::remoteIP6() {
1280 | if (!_pcb) {
1281 | return IPAddress(IPType::IPv6);
1282 | }
1283 | IPAddress ip;
1284 | ip.from_ip_addr_t(&(_pcb->remote_ip));
1285 | return ip;
1286 | }
1287 |
1288 | IPAddress AsyncClient::localIP6() {
1289 | if (!_pcb) {
1290 | return IPAddress(IPType::IPv6);
1291 | }
1292 | IPAddress ip;
1293 | ip.from_ip_addr_t(&(_pcb->local_ip));
1294 | return ip;
1295 | }
1296 | #endif
1297 | #endif
1298 |
1299 | uint16_t AsyncClient::getRemotePort() {
1300 | if (!_pcb) {
1301 | return 0;
1302 | }
1303 | return _pcb->remote_port;
1304 | }
1305 |
1306 | uint32_t AsyncClient::getLocalAddress() {
1307 | if (!_pcb) {
1308 | return 0;
1309 | }
1310 | #if LWIP_IPV4 && LWIP_IPV6
1311 | return _pcb->local_ip.u_addr.ip4.addr;
1312 | #else
1313 | return _pcb->local_ip.addr;
1314 | #endif
1315 | }
1316 |
1317 | uint16_t AsyncClient::getLocalPort() {
1318 | if (!_pcb) {
1319 | return 0;
1320 | }
1321 | return _pcb->local_port;
1322 | }
1323 |
1324 | IPAddress AsyncClient::remoteIP() {
1325 | #if ESP_IDF_VERSION_MAJOR < 5
1326 | return IPAddress(getRemoteAddress());
1327 | #else
1328 | if (!_pcb) {
1329 | return IPAddress();
1330 | }
1331 | IPAddress ip;
1332 | ip.from_ip_addr_t(&(_pcb->remote_ip));
1333 | return ip;
1334 | #endif
1335 | }
1336 |
1337 | uint16_t AsyncClient::remotePort() {
1338 | return getRemotePort();
1339 | }
1340 |
1341 | IPAddress AsyncClient::localIP() {
1342 | #if ESP_IDF_VERSION_MAJOR < 5
1343 | return IPAddress(getLocalAddress());
1344 | #else
1345 | if (!_pcb) {
1346 | return IPAddress();
1347 | }
1348 | IPAddress ip;
1349 | ip.from_ip_addr_t(&(_pcb->local_ip));
1350 | return ip;
1351 | #endif
1352 | }
1353 |
1354 | uint16_t AsyncClient::localPort() {
1355 | return getLocalPort();
1356 | }
1357 |
1358 | uint8_t AsyncClient::state() {
1359 | if (!_pcb) {
1360 | return 0;
1361 | }
1362 | return _pcb->state;
1363 | }
1364 |
1365 | bool AsyncClient::connected() {
1366 | if (!_pcb) {
1367 | return false;
1368 | }
1369 | return _pcb->state == ESTABLISHED;
1370 | }
1371 |
1372 | bool AsyncClient::connecting() {
1373 | if (!_pcb) {
1374 | return false;
1375 | }
1376 | return _pcb->state > CLOSED && _pcb->state < ESTABLISHED;
1377 | }
1378 |
1379 | bool AsyncClient::disconnecting() {
1380 | if (!_pcb) {
1381 | return false;
1382 | }
1383 | return _pcb->state > ESTABLISHED && _pcb->state < TIME_WAIT;
1384 | }
1385 |
1386 | bool AsyncClient::disconnected() {
1387 | if (!_pcb) {
1388 | return true;
1389 | }
1390 | return _pcb->state == CLOSED || _pcb->state == TIME_WAIT;
1391 | }
1392 |
1393 | bool AsyncClient::freeable() {
1394 | if (!_pcb) {
1395 | return true;
1396 | }
1397 | return _pcb->state == CLOSED || _pcb->state > ESTABLISHED;
1398 | }
1399 |
1400 | bool AsyncClient::canSend() {
1401 | return space() > 0;
1402 | }
1403 |
1404 | const char* AsyncClient::errorToString(int8_t error) {
1405 | switch (error) {
1406 | case ERR_OK:
1407 | return "OK";
1408 | case ERR_MEM:
1409 | return "Out of memory error";
1410 | case ERR_BUF:
1411 | return "Buffer error";
1412 | case ERR_TIMEOUT:
1413 | return "Timeout";
1414 | case ERR_RTE:
1415 | return "Routing problem";
1416 | case ERR_INPROGRESS:
1417 | return "Operation in progress";
1418 | case ERR_VAL:
1419 | return "Illegal value";
1420 | case ERR_WOULDBLOCK:
1421 | return "Operation would block";
1422 | case ERR_USE:
1423 | return "Address in use";
1424 | case ERR_ALREADY:
1425 | return "Already connected";
1426 | case ERR_CONN:
1427 | return "Not connected";
1428 | case ERR_IF:
1429 | return "Low-level netif error";
1430 | case ERR_ABRT:
1431 | return "Connection aborted";
1432 | case ERR_RST:
1433 | return "Connection reset";
1434 | case ERR_CLSD:
1435 | return "Connection closed";
1436 | case ERR_ARG:
1437 | return "Illegal argument";
1438 | case -55:
1439 | return "DNS failed";
1440 | default:
1441 | return "UNKNOWN";
1442 | }
1443 | }
1444 |
1445 | const char* AsyncClient::stateToString() {
1446 | switch (state()) {
1447 | case 0:
1448 | return "Closed";
1449 | case 1:
1450 | return "Listen";
1451 | case 2:
1452 | return "SYN Sent";
1453 | case 3:
1454 | return "SYN Received";
1455 | case 4:
1456 | return "Established";
1457 | case 5:
1458 | return "FIN Wait 1";
1459 | case 6:
1460 | return "FIN Wait 2";
1461 | case 7:
1462 | return "Close Wait";
1463 | case 8:
1464 | return "Closing";
1465 | case 9:
1466 | return "Last ACK";
1467 | case 10:
1468 | return "Time Wait";
1469 | default:
1470 | return "UNKNOWN";
1471 | }
1472 | }
1473 |
1474 | /*
1475 | * Static Callbacks (LwIP C2C++ interconnect)
1476 | * */
1477 |
1478 | void AsyncClient::_s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg) {
1479 | reinterpret_cast(arg)->_dns_found(ipaddr);
1480 | }
1481 |
1482 | int8_t AsyncClient::_s_poll(void* arg, struct tcp_pcb* pcb) {
1483 | return reinterpret_cast(arg)->_poll(pcb);
1484 | }
1485 |
1486 | int8_t AsyncClient::_s_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* pb, int8_t err) {
1487 | return reinterpret_cast(arg)->_recv(pcb, pb, err);
1488 | }
1489 |
1490 | int8_t AsyncClient::_s_fin(void* arg, struct tcp_pcb* pcb, int8_t err) {
1491 | return reinterpret_cast(arg)->_fin(pcb, err);
1492 | }
1493 |
1494 | int8_t AsyncClient::_s_lwip_fin(void* arg, struct tcp_pcb* pcb, int8_t err) {
1495 | return reinterpret_cast(arg)->_lwip_fin(pcb, err);
1496 | }
1497 |
1498 | int8_t AsyncClient::_s_sent(void* arg, struct tcp_pcb* pcb, uint16_t len) {
1499 | return reinterpret_cast(arg)->_sent(pcb, len);
1500 | }
1501 |
1502 | void AsyncClient::_s_error(void* arg, int8_t err) {
1503 | reinterpret_cast(arg)->_error(err);
1504 | }
1505 |
1506 | int8_t AsyncClient::_s_connected(void* arg, struct tcp_pcb* pcb, int8_t err) {
1507 | return reinterpret_cast(arg)->_connected(pcb, err);
1508 | }
1509 |
1510 | /*
1511 | Async TCP Server
1512 | */
1513 |
1514 | AsyncServer::AsyncServer(IPAddress addr, uint16_t port)
1515 | : _port(port)
1516 | #if ESP_IDF_VERSION_MAJOR < 5
1517 | ,
1518 | _bind4(true), _bind6(false)
1519 | #else
1520 | ,
1521 | _bind4(addr.type() != IPType::IPv6), _bind6(addr.type() == IPType::IPv6)
1522 | #endif
1523 | ,
1524 | _addr(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) {
1525 | }
1526 |
1527 | #if ESP_IDF_VERSION_MAJOR < 5
1528 | AsyncServer::AsyncServer(IPv6Address addr, uint16_t port)
1529 | : _port(port), _bind4(false), _bind6(true), _addr6(addr), _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) {}
1530 | #endif
1531 |
1532 | AsyncServer::AsyncServer(uint16_t port)
1533 | : _port(port), _bind4(true), _bind6(false), _addr((uint32_t)IPADDR_ANY)
1534 | #if ESP_IDF_VERSION_MAJOR < 5
1535 | ,
1536 | _addr6()
1537 | #endif
1538 | ,
1539 | _noDelay(false), _pcb(0), _connect_cb(0), _connect_cb_arg(0) {
1540 | }
1541 |
1542 | AsyncServer::~AsyncServer() {
1543 | end();
1544 | }
1545 |
1546 | void AsyncServer::onClient(AcConnectHandler cb, void* arg) {
1547 | _connect_cb = cb;
1548 | _connect_cb_arg = arg;
1549 | }
1550 |
1551 | void AsyncServer::begin() {
1552 | if (_pcb) {
1553 | return;
1554 | }
1555 |
1556 | if (!_start_async_task()) {
1557 | log_e("failed to start task");
1558 | return;
1559 | }
1560 | int8_t err;
1561 | TCP_MUTEX_LOCK();
1562 | _pcb = tcp_new_ip_type(_bind4 && _bind6 ? IPADDR_TYPE_ANY : (_bind6 ? IPADDR_TYPE_V6 : IPADDR_TYPE_V4));
1563 | TCP_MUTEX_UNLOCK();
1564 | if (!_pcb) {
1565 | log_e("_pcb == NULL");
1566 | return;
1567 | }
1568 |
1569 | ip_addr_t local_addr;
1570 | #if ESP_IDF_VERSION_MAJOR < 5
1571 | if (_bind6) { // _bind6 && _bind4 both at the same time is not supported on Arduino 2 in this lib API
1572 | local_addr.type = IPADDR_TYPE_V6;
1573 | memcpy(local_addr.u_addr.ip6.addr, static_cast(_addr6), sizeof(uint32_t) * 4);
1574 | } else {
1575 | local_addr.type = IPADDR_TYPE_V4;
1576 | local_addr.u_addr.ip4.addr = _addr;
1577 | }
1578 | #else
1579 | _addr.to_ip_addr_t(&local_addr);
1580 | #endif
1581 | err = _tcp_bind(_pcb, &local_addr, _port);
1582 |
1583 | if (err != ERR_OK) {
1584 | _tcp_close(_pcb, -1);
1585 | log_e("bind error: %d", err);
1586 | return;
1587 | }
1588 |
1589 | static uint8_t backlog = 5;
1590 | _pcb = _tcp_listen_with_backlog(_pcb, backlog);
1591 | if (!_pcb) {
1592 | log_e("listen_pcb == NULL");
1593 | return;
1594 | }
1595 | TCP_MUTEX_LOCK();
1596 | tcp_arg(_pcb, (void*)this);
1597 | tcp_accept(_pcb, &_s_accept);
1598 | TCP_MUTEX_UNLOCK();
1599 | }
1600 |
1601 | void AsyncServer::end() {
1602 | if (_pcb) {
1603 | TCP_MUTEX_LOCK();
1604 | tcp_arg(_pcb, NULL);
1605 | tcp_accept(_pcb, NULL);
1606 | if (tcp_close(_pcb) != ERR_OK) {
1607 | TCP_MUTEX_UNLOCK();
1608 | _tcp_abort(_pcb, -1);
1609 | } else {
1610 | TCP_MUTEX_UNLOCK();
1611 | }
1612 | _pcb = NULL;
1613 | }
1614 | }
1615 |
1616 | // runs on LwIP thread
1617 | int8_t AsyncServer::_accept(tcp_pcb* pcb, int8_t err) {
1618 | // ets_printf("+A: 0x%08x\n", pcb);
1619 | if (_connect_cb) {
1620 | AsyncClient* c = new AsyncClient(pcb);
1621 | if (c) {
1622 | c->setNoDelay(_noDelay);
1623 | return _tcp_accept(this, c);
1624 | }
1625 | }
1626 | if (tcp_close(pcb) != ERR_OK) {
1627 | tcp_abort(pcb);
1628 | }
1629 | log_d("FAIL");
1630 | return ERR_OK;
1631 | }
1632 |
1633 | int8_t AsyncServer::_accepted(AsyncClient* client) {
1634 | if (_connect_cb) {
1635 | _connect_cb(_connect_cb_arg, client);
1636 | }
1637 | return ERR_OK;
1638 | }
1639 |
1640 | void AsyncServer::setNoDelay(bool nodelay) {
1641 | _noDelay = nodelay;
1642 | }
1643 |
1644 | bool AsyncServer::getNoDelay() {
1645 | return _noDelay;
1646 | }
1647 |
1648 | uint8_t AsyncServer::status() {
1649 | if (!_pcb) {
1650 | return 0;
1651 | }
1652 | return _pcb->state;
1653 | }
1654 |
1655 | int8_t AsyncServer::_s_accept(void* arg, tcp_pcb* pcb, int8_t err) {
1656 | return reinterpret_cast(arg)->_accept(pcb, err);
1657 | }
1658 |
1659 | int8_t AsyncServer::_s_accepted(void* arg, AsyncClient* client) {
1660 | return reinterpret_cast(arg)->_accepted(client);
1661 | }
1662 |
--------------------------------------------------------------------------------
/src/AsyncTCP.h:
--------------------------------------------------------------------------------
1 | /*
2 | Asynchronous TCP library for Espressif MCUs
3 |
4 | Copyright (c) 2016 Hristo Gochkov. All rights reserved.
5 | This file is part of the esp8266 core for Arduino environment.
6 |
7 | This library is free software; you can redistribute it and/or
8 | modify it under the terms of the GNU Lesser General Public
9 | License as published by the Free Software Foundation; either
10 | version 2.1 of the License, or (at your option) any later version.
11 |
12 | This library is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 | Lesser General Public License for more details.
16 |
17 | You should have received a copy of the GNU Lesser General Public
18 | License along with this library; if not, write to the Free Software
19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 | */
21 |
22 | #ifndef ASYNCTCP_H_
23 | #define ASYNCTCP_H_
24 |
25 | #define ASYNCTCP_VERSION "3.3.2"
26 | #define ASYNCTCP_VERSION_MAJOR 3
27 | #define ASYNCTCP_VERSION_MINOR 3
28 | #define ASYNCTCP_VERSION_REVISION 2
29 | #define ASYNCTCP_FORK_ESP32Async
30 |
31 | #include "IPAddress.h"
32 | #if ESP_IDF_VERSION_MAJOR < 5
33 | #include "IPv6Address.h"
34 | #endif
35 | #include "lwip/ip6_addr.h"
36 | #include "lwip/ip_addr.h"
37 | #include
38 |
39 | #ifndef LIBRETINY
40 | #include "sdkconfig.h"
41 | extern "C" {
42 | #include "freertos/semphr.h"
43 | #include "lwip/pbuf.h"
44 | }
45 | #else
46 | extern "C" {
47 | #include
48 | #include
49 | }
50 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
51 | #endif
52 |
53 | // If core is not defined, then we are running in Arduino or PIO
54 | #ifndef CONFIG_ASYNC_TCP_RUNNING_CORE
55 | #define CONFIG_ASYNC_TCP_RUNNING_CORE -1 // any available core
56 | #endif
57 |
58 | // guard AsyncTCP task with watchdog
59 | #ifndef CONFIG_ASYNC_TCP_USE_WDT
60 | #define CONFIG_ASYNC_TCP_USE_WDT 1
61 | #endif
62 |
63 | #ifndef CONFIG_ASYNC_TCP_STACK_SIZE
64 | #define CONFIG_ASYNC_TCP_STACK_SIZE 8192 * 2
65 | #endif
66 |
67 | #ifndef CONFIG_ASYNC_TCP_PRIORITY
68 | #define CONFIG_ASYNC_TCP_PRIORITY 10
69 | #endif
70 |
71 | #ifndef CONFIG_ASYNC_TCP_QUEUE_SIZE
72 | #define CONFIG_ASYNC_TCP_QUEUE_SIZE 64
73 | #endif
74 |
75 | #ifndef CONFIG_ASYNC_TCP_MAX_ACK_TIME
76 | #define CONFIG_ASYNC_TCP_MAX_ACK_TIME 5000
77 | #endif
78 |
79 | class AsyncClient;
80 |
81 | #define ASYNC_WRITE_FLAG_COPY 0x01 // will allocate new buffer to hold the data while sending (else will hold reference to the data given)
82 | #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.
83 |
84 | typedef std::function AcConnectHandler;
85 | typedef std::function AcAckHandler;
86 | typedef std::function AcErrorHandler;
87 | typedef std::function AcDataHandler;
88 | typedef std::function AcPacketHandler;
89 | typedef std::function AcTimeoutHandler;
90 |
91 | struct tcp_pcb;
92 | struct ip_addr;
93 |
94 | class AsyncClient {
95 | public:
96 | AsyncClient(tcp_pcb* pcb = 0);
97 | ~AsyncClient();
98 |
99 | AsyncClient& operator=(const AsyncClient& other);
100 | AsyncClient& operator+=(const AsyncClient& other);
101 |
102 | bool operator==(const AsyncClient& other);
103 |
104 | bool operator!=(const AsyncClient& other) {
105 | return !(*this == other);
106 | }
107 | bool connect(const IPAddress& ip, uint16_t port);
108 | #if ESP_IDF_VERSION_MAJOR < 5
109 | bool connect(const IPv6Address& ip, uint16_t port);
110 | #endif
111 | bool connect(const char* host, uint16_t port);
112 | /**
113 | * @brief close connection
114 | *
115 | * @param now - ignored
116 | */
117 | void close(bool now = false);
118 | // same as close()
119 | void stop() { close(false); };
120 | int8_t abort();
121 | bool free();
122 |
123 | // ack is not pending
124 | bool canSend();
125 | // TCP buffer space available
126 | size_t space();
127 |
128 | /**
129 | * @brief add data to be send (but do not send yet)
130 | * @note add() would call lwip's tcp_write()
131 | By default apiflags=ASYNC_WRITE_FLAG_COPY
132 | You could try to use apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
133 | but looks like it does not work for Arduino's lwip in ESP32/IDF at least
134 | it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
135 | if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
136 | https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
137 | *
138 | * @param data
139 | * @param size
140 | * @param apiflags
141 | * @return size_t amount of data that has been copied
142 | */
143 | size_t add(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
144 |
145 | /**
146 | * @brief send data previously add()'ed
147 | *
148 | * @return true on success
149 | * @return false on error
150 | */
151 | bool send();
152 |
153 | /**
154 | * @brief add and enqueue data for sending
155 | * @note it is same as add() + send()
156 | * @note only make sense when canSend() == true
157 | *
158 | * @param data
159 | * @param size
160 | * @param apiflags
161 | * @return size_t
162 | */
163 | size_t write(const char* data, size_t size, uint8_t apiflags = ASYNC_WRITE_FLAG_COPY);
164 |
165 | /**
166 | * @brief add and enque data for sending
167 | * @note treats data as null-terminated string
168 | *
169 | * @param data
170 | * @return size_t
171 | */
172 | size_t write(const char* data) { return data == NULL ? 0 : write(data, strlen(data)); };
173 |
174 | uint8_t state();
175 | bool connecting();
176 | bool connected();
177 | bool disconnecting();
178 | bool disconnected();
179 |
180 | // disconnected or disconnecting
181 | bool freeable();
182 |
183 | uint16_t getMss();
184 |
185 | uint32_t getRxTimeout();
186 | // no RX data timeout for the connection in seconds
187 | void setRxTimeout(uint32_t timeout);
188 |
189 | uint32_t getAckTimeout();
190 | // no ACK timeout for the last sent packet in milliseconds
191 | void setAckTimeout(uint32_t timeout);
192 |
193 | void setNoDelay(bool nodelay);
194 | bool getNoDelay();
195 |
196 | void setKeepAlive(uint32_t ms, uint8_t cnt);
197 |
198 | uint32_t getRemoteAddress();
199 | uint16_t getRemotePort();
200 | uint32_t getLocalAddress();
201 | uint16_t getLocalPort();
202 | #if LWIP_IPV6
203 | ip6_addr_t getRemoteAddress6();
204 | ip6_addr_t getLocalAddress6();
205 | #if ESP_IDF_VERSION_MAJOR < 5
206 | IPv6Address remoteIP6();
207 | IPv6Address localIP6();
208 | #else
209 | IPAddress remoteIP6();
210 | IPAddress localIP6();
211 | #endif
212 | #endif
213 |
214 | // compatibility
215 | IPAddress remoteIP();
216 | uint16_t remotePort();
217 | IPAddress localIP();
218 | uint16_t localPort();
219 |
220 | // set callback - on successful connect
221 | void onConnect(AcConnectHandler cb, void* arg = 0);
222 | // set callback - disconnected
223 | void onDisconnect(AcConnectHandler cb, void* arg = 0);
224 | // set callback - ack received
225 | void onAck(AcAckHandler cb, void* arg = 0);
226 | // set callback - unsuccessful connect or error
227 | void onError(AcErrorHandler cb, void* arg = 0);
228 | // set callback - data received (called if onPacket is not used)
229 | void onData(AcDataHandler cb, void* arg = 0);
230 | // set callback - data received
231 | void onPacket(AcPacketHandler cb, void* arg = 0);
232 | // set callback - ack timeout
233 | void onTimeout(AcTimeoutHandler cb, void* arg = 0);
234 | // set callback - every 125ms when connected
235 | void onPoll(AcConnectHandler cb, void* arg = 0);
236 |
237 | // ack pbuf from onPacket
238 | void ackPacket(struct pbuf* pb);
239 | // ack data that you have not acked using the method below
240 | size_t ack(size_t len);
241 | // will not ack the current packet. Call from onData
242 | void ackLater() { _ack_pcb = false; }
243 |
244 | static const char* errorToString(int8_t error);
245 | const char* stateToString();
246 |
247 | // internal callbacks - Do NOT call any of the functions below in user code!
248 | static int8_t _s_poll(void* arg, struct tcp_pcb* tpcb);
249 | static int8_t _s_recv(void* arg, struct tcp_pcb* tpcb, struct pbuf* pb, int8_t err);
250 | static int8_t _s_fin(void* arg, struct tcp_pcb* tpcb, int8_t err);
251 | static int8_t _s_lwip_fin(void* arg, struct tcp_pcb* tpcb, int8_t err);
252 | static void _s_error(void* arg, int8_t err);
253 | static int8_t _s_sent(void* arg, struct tcp_pcb* tpcb, uint16_t len);
254 | static int8_t _s_connected(void* arg, struct tcp_pcb* tpcb, int8_t err);
255 | static void _s_dns_found(const char* name, struct ip_addr* ipaddr, void* arg);
256 |
257 | int8_t _recv(tcp_pcb* pcb, pbuf* pb, int8_t err);
258 | tcp_pcb* pcb() { return _pcb; }
259 |
260 | protected:
261 | bool _connect(ip_addr_t addr, uint16_t port);
262 |
263 | tcp_pcb* _pcb;
264 | int8_t _closed_slot;
265 |
266 | AcConnectHandler _connect_cb;
267 | void* _connect_cb_arg;
268 | AcConnectHandler _discard_cb;
269 | void* _discard_cb_arg;
270 | AcAckHandler _sent_cb;
271 | void* _sent_cb_arg;
272 | AcErrorHandler _error_cb;
273 | void* _error_cb_arg;
274 | AcDataHandler _recv_cb;
275 | void* _recv_cb_arg;
276 | AcPacketHandler _pb_cb;
277 | void* _pb_cb_arg;
278 | AcTimeoutHandler _timeout_cb;
279 | void* _timeout_cb_arg;
280 | AcConnectHandler _poll_cb;
281 | void* _poll_cb_arg;
282 |
283 | bool _ack_pcb;
284 | uint32_t _tx_last_packet;
285 | uint32_t _rx_ack_len;
286 | uint32_t _rx_last_packet;
287 | uint32_t _rx_timeout;
288 | uint32_t _rx_last_ack;
289 | uint32_t _ack_timeout;
290 | uint16_t _connect_port;
291 |
292 | int8_t _close();
293 | void _free_closed_slot();
294 | bool _allocate_closed_slot();
295 | int8_t _connected(tcp_pcb* pcb, int8_t err);
296 | void _error(int8_t err);
297 | int8_t _poll(tcp_pcb* pcb);
298 | int8_t _sent(tcp_pcb* pcb, uint16_t len);
299 | int8_t _fin(tcp_pcb* pcb, int8_t err);
300 | int8_t _lwip_fin(tcp_pcb* pcb, int8_t err);
301 | void _dns_found(struct ip_addr* ipaddr);
302 |
303 | public:
304 | AsyncClient* prev;
305 | AsyncClient* next;
306 | };
307 |
308 | class AsyncServer {
309 | public:
310 | AsyncServer(IPAddress addr, uint16_t port);
311 | #if ESP_IDF_VERSION_MAJOR < 5
312 | AsyncServer(IPv6Address addr, uint16_t port);
313 | #endif
314 | AsyncServer(uint16_t port);
315 | ~AsyncServer();
316 | void onClient(AcConnectHandler cb, void* arg);
317 | void begin();
318 | void end();
319 | void setNoDelay(bool nodelay);
320 | bool getNoDelay();
321 | uint8_t status();
322 |
323 | // Do not use any of the functions below!
324 | static int8_t _s_accept(void* arg, tcp_pcb* newpcb, int8_t err);
325 | static int8_t _s_accepted(void* arg, AsyncClient* client);
326 |
327 | protected:
328 | uint16_t _port;
329 | bool _bind4 = false;
330 | bool _bind6 = false;
331 | IPAddress _addr;
332 | #if ESP_IDF_VERSION_MAJOR < 5
333 | IPv6Address _addr6;
334 | #endif
335 | bool _noDelay;
336 | tcp_pcb* _pcb;
337 | AcConnectHandler _connect_cb;
338 | void* _connect_cb_arg;
339 |
340 | int8_t _accept(tcp_pcb* newpcb, int8_t err);
341 | int8_t _accepted(AsyncClient* client);
342 | };
343 |
344 | #endif /* ASYNCTCP_H_ */
345 |
--------------------------------------------------------------------------------