diff --git a/.github/workflows/build_and_run_example_test_mdns.yml b/.github/workflows/build_and_run_example_test_mdns.yml
index de4aac90e..3d309a6f5 100644
--- a/.github/workflows/build_and_run_example_test_mdns.yml
+++ b/.github/workflows/build_and_run_example_test_mdns.yml
@@ -13,8 +13,9 @@ jobs:
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
- uses: actions/checkout@master
+ uses: actions/checkout@v3
with:
+ submodules: recursive
path: esp-protocols
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
env:
@@ -23,6 +24,7 @@ jobs:
run: |
. ${IDF_PATH}/export.sh
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/examples/
+ idf.py set-target ${{ matrix.idf_target }}
cat sdkconfig.ci.eth_def >> sdkconfig.defaults
idf.py build
rm sdkconfig.defaults
@@ -32,4 +34,5 @@ jobs:
cat sdkconfig.ci.eth_socket >> sdkconfig.defaults
idf.py build
cd $GITHUB_WORKSPACE/esp-protocols/components/mdns/tests/test_apps/
+ idf.py set-target ${{ matrix.idf_target }}
idf.py build
diff --git a/.github/workflows/build_and_run_example_test.yml b/.github/workflows/build_and_run_example_test_websocket.yml
similarity index 97%
rename from .github/workflows/build_and_run_example_test.yml
rename to .github/workflows/build_and_run_example_test_websocket.yml
index 4f26acd48..678b6d1e6 100644
--- a/.github/workflows/build_and_run_example_test.yml
+++ b/.github/workflows/build_and_run_example_test_websocket.yml
@@ -13,7 +13,9 @@ jobs:
container: espressif/idf:${{ matrix.idf_ver }}
steps:
- name: Checkout esp-protocols
- uses: actions/checkout@v1
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
env:
IDF_TARGET: ${{ matrix.idf_target }}
diff --git a/.github/workflows/build_asio.yml b/.github/workflows/build_asio.yml
new file mode 100644
index 000000000..83ccf2d94
--- /dev/null
+++ b/.github/workflows/build_asio.yml
@@ -0,0 +1,28 @@
+name: Build ASIO
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ idf_ver: ["latest"]
+ idf_target: ["esp32", "esp32s2"]
+ example: ["asio_chat", "async_request", "socks4", "ssl_client_server", "tcp_echo_server", "udp_echo_server"]
+
+ runs-on: ubuntu-20.04
+ container: espressif/idf:${{ matrix.idf_ver }}
+ steps:
+ - name: Checkout esp-protocols
+ uses: actions/checkout@v3
+ with:
+ path: esp-protocols
+ - name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }} for ${{ matrix.idf_target }}
+ env:
+ IDF_TARGET: ${{ matrix.idf_target }}
+ shell: bash
+ run: |
+ . ${IDF_PATH}/export.sh
+ cd $GITHUB_WORKSPACE/esp-protocols/components/asio/examples/${{ matrix.example }}
+ idf.py set-target ${{ matrix.idf_target }}
+ idf.py build
diff --git a/.github/workflows/publish-docs-component.yml b/.github/workflows/publish-docs-component.yml
index fbc674a2d..0eeda079c 100644
--- a/.github/workflows/publish-docs-component.yml
+++ b/.github/workflows/publish-docs-component.yml
@@ -39,10 +39,16 @@ jobs:
cp -r html_en/. $GITHUB_WORKSPACE/docs/mdns/en
cp -r html_zh_CN/. $GITHUB_WORKSPACE/docs/mdns/zh_CN
+ cd $GITHUB_WORKSPACE/components/asio/docs
+ ./generate_docs
+ mkdir -p $GITHUB_WORKSPACE/docs/asio
+ cp -r html/. $GITHUB_WORKSPACE/docs/asio
+
cd $GITHUB_WORKSPACE/docs
touch .nojekyll
echo 'esp-modem
' > index.html
echo 'esp-websocket-client
' >> index.html
+ echo 'ASIO
' >> index.html
echo 'mDNS_en
' >> index.html
echo 'mDNS_zh_CN
' >> index.html
@@ -50,7 +56,7 @@ jobs:
- name: Upload components to component service
uses: espressif/github-actions/upload_components@master
with:
- directories: "components/esp_modem;components/esp_websocket_client;components/mdns"
+ directories: "components/esp_modem;components/esp_websocket_client;components/mdns;components/asio"
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..6ecce627b
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "components/asio/asio"]
+ path = components/asio/asio
+ url = https://github.com/espressif/asio
diff --git a/README.md b/README.md
index c886af4b8..c422581ad 100644
--- a/README.md
+++ b/README.md
@@ -19,3 +19,8 @@
* Brief introduction [README](components/esp_websocket_client/README.md)
* Full html [documentation](https://espressif.github.io/esp-protocols/esp_websocket_client/index.html)
+
+### ASIO port
+
+* Brief introduction [README](components/asio/README.md)
+* Full html [documentation](https://espressif.github.io/esp-protocols/asio/index.html)
diff --git a/components/asio/CMakeLists.txt b/components/asio/CMakeLists.txt
new file mode 100644
index 000000000..336654597
--- /dev/null
+++ b/components/asio/CMakeLists.txt
@@ -0,0 +1,44 @@
+if(NOT CONFIG_LWIP_IPV6 AND NOT CMAKE_BUILD_EARLY_EXPANSION)
+ # note: the component is still included in the build so it can become visible again in config
+ # without needing to re-run CMake. However no source or header files are built.
+ message(STATUS "IPV6 support is disabled so the asio component will not be built")
+ idf_component_register()
+ return()
+endif()
+
+set(asio_sources "asio/asio/src/asio.cpp")
+
+if(CONFIG_ASIO_SSL_SUPPORT)
+ if(CONFIG_ASIO_USE_ESP_OPENSSL)
+ list(APPEND asio_sources
+ "port/src/asio_ssl_impl.cpp"
+ "port/mbedtls/src/mbedtls_context.cpp"
+ "port/mbedtls/src/mbedtls_engine.cpp")
+ set(asio_priv_includes "port/mbedtls/include")
+ endif()
+
+ if(CONFIG_ASIO_USE_ESP_WOLFSSL)
+ list(APPEND asio_sources
+ "asio/asio/src/asio_ssl.cpp")
+ endif()
+endif()
+
+idf_component_register(SRCS ${asio_sources}
+ INCLUDE_DIRS "asio/asio/include" "port/include"
+ PRIV_INCLUDE_DIRS ${asio_priv_includes}
+ REQUIRES lwip)
+
+if(CONFIG_ASIO_SSL_SUPPORT)
+ if(CONFIG_ASIO_USE_ESP_WOLFSSL)
+ idf_component_get_property(wolflib esp-wolfssl COMPONENT_LIB)
+ idf_component_get_property(wolfdir esp-wolfssl COMPONENT_DIR)
+
+ target_link_libraries(${COMPONENT_LIB} PUBLIC ${wolflib})
+ target_include_directories(${COMPONENT_LIB} PUBLIC ${wolfdir}/wolfssl/wolfssl)
+ endif()
+
+ if(CONFIG_ASIO_USE_ESP_OPENSSL)
+ idf_component_get_property(mbedtls mbedtls COMPONENT_LIB)
+ target_link_libraries(${COMPONENT_LIB} PUBLIC ${mbedtls})
+ endif()
+endif()
diff --git a/components/asio/Kconfig b/components/asio/Kconfig
new file mode 100644
index 000000000..6754e1aa2
--- /dev/null
+++ b/components/asio/Kconfig
@@ -0,0 +1,36 @@
+menu "ESP-ASIO"
+ visible if LWIP_IPV6
+
+ config ASIO_SSL_SUPPORT
+ bool "Enable SSL/TLS support of ASIO"
+ default n
+ help
+ Enable support for basic SSL/TLS features, available for mbedTLS/OpenSSL
+ as well as wolfSSL TLS library.
+
+ choice ASIO_SSL_LIBRARY_CHOICE
+ prompt "Choose SSL/TLS library for ESP-TLS (See help for more Info)"
+ default ASIO_USE_ESP_OPENSSL
+ depends on ASIO_SSL_SUPPORT
+ help
+ The ASIO support multiple backend TLS libraries. Currently the mbedTLS with a thin ESP-OpenSSL
+ port layer (default choice) and WolfSSL are supported.
+ Different TLS libraries may support different features and have different resource
+ usage. Consult the ESP-TLS documentation in ESP-IDF Programming guide for more details.
+ config ASIO_USE_ESP_OPENSSL
+ bool "esp-openssl"
+ config ASIO_USE_ESP_WOLFSSL
+ depends on TLS_STACK_WOLFSSL
+ bool "wolfSSL (License info in wolfSSL directory README)"
+ endchoice
+
+ config ASIO_SSL_BIO_SIZE
+ int "Size of BIO object"
+ default 1024
+ depends on ASIO_SSL_SUPPORT
+ help
+ Size in bytes of SSL-BIO implementation.
+ Reducing the BIO size saves more RAM, but may slow down input output operations due to
+ fragmentation.
+
+endmenu
diff --git a/components/asio/README.md b/components/asio/README.md
new file mode 100644
index 000000000..acfbd5f91
--- /dev/null
+++ b/components/asio/README.md
@@ -0,0 +1,11 @@
+# ASIO port
+
+Asio is a cross-platform C++ library, see https://think-async.com/Asio/. It provides a consistent asynchronous model using a modern C++ approach.
+
+## Examples
+
+Get started with example test :example:`examples `:
+
+## Documentation
+
+* View the full [html documentation](https://espressif.github.io/esp-protocols/asio/index.html)
diff --git a/components/asio/asio b/components/asio/asio
new file mode 160000
index 000000000..58384fb6a
--- /dev/null
+++ b/components/asio/asio
@@ -0,0 +1 @@
+Subproject commit 58384fb6af92e7952b1534acb3004924f7ea0565
diff --git a/components/asio/docs/Doxyfile b/components/asio/docs/Doxyfile
new file mode 100755
index 000000000..1e95d8217
--- /dev/null
+++ b/components/asio/docs/Doxyfile
@@ -0,0 +1,73 @@
+# This is Doxygen configuration file
+#
+# Doxygen provides over 260 configuration statements
+# To make this file easier to follow,
+# it contains only statements that are non-default
+#
+# NOTE:
+# It is recommended not to change defaults unless specifically required
+# Test any changes how they affect generated documentation
+# Make sure that correct warnings are generated to flag issues with documented code
+#
+# For the complete list of configuration statements see:
+# http://doxygen.nl/manual/config.html
+
+
+PROJECT_NAME = "ESP Protocols Programming Guide"
+
+## The 'INPUT' statement below is used as input by script 'gen-df-input.py'
+## to automatically generate API reference list files heder_file.inc
+## These files are placed in '_inc' directory
+## and used to include in API reference documentation
+
+
+## Get warnings for functions that have no documentation for their parameters or return value
+##
+WARN_NO_PARAMDOC = YES
+
+## Enable preprocessing and remove __attribute__(...) expressions from the INPUT files
+##
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = YES
+PREDEFINED = \
+ $(ENV_DOXYGEN_DEFINES) \
+ __DOXYGEN__=1 \
+ __attribute__(x)= \
+ _Static_assert()= \
+ IDF_DEPRECATED(X)= \
+ IRAM_ATTR= \
+ configSUPPORT_DYNAMIC_ALLOCATION=1 \
+ configSUPPORT_STATIC_ALLOCATION=1 \
+ configQUEUE_REGISTRY_SIZE=1 \
+ configUSE_RECURSIVE_MUTEXES=1 \
+ configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS=1 \
+ configNUM_THREAD_LOCAL_STORAGE_POINTERS=1 \
+ configUSE_APPLICATION_TASK_TAG=1 \
+ configTASKLIST_INCLUDE_COREID=1 \
+ "ESP_EVENT_DECLARE_BASE(x)=extern esp_event_base_t x"
+
+## Do not complain about not having dot
+##
+HAVE_DOT = NO
+
+## Generate XML that is required for Breathe
+##
+GENERATE_XML = YES
+XML_OUTPUT = xml
+
+GENERATE_HTML = NO
+HAVE_DOT = NO
+GENERATE_LATEX = NO
+GENERATE_MAN = YES
+GENERATE_RTF = NO
+
+## Skip distracting progress messages
+##
+QUIET = YES
+
+## Enable Section Tags for conditional documentation
+##
+ENABLED_SECTIONS += \
+ DOC_EXCLUDE_HEADER_SECTION \ ## To conditionally remove doc sections from IDF source files without affecting documentation in upstream files.
+ DOC_SINGLE_GROUP ## To conditionally remove groups from the documentation and create a 'flat' document without affecting documentation in upstream files.
diff --git a/components/asio/docs/conf_common.py b/components/asio/docs/conf_common.py
new file mode 100644
index 000000000..10d1e997e
--- /dev/null
+++ b/components/asio/docs/conf_common.py
@@ -0,0 +1,21 @@
+from esp_docs.conf_docs import * # noqa: F403,F401
+
+extensions += ['sphinx_copybutton',
+ # Needed as a trigger for running doxygen
+ 'esp_docs.esp_extensions.dummy_build_system',
+ 'esp_docs.esp_extensions.run_doxygen',
+ ]
+
+# link roles config
+github_repo = 'espressif/esp-protocols'
+
+# context used by sphinx_idf_theme
+html_context['github_user'] = 'espressif'
+html_context['github_repo'] = 'esp-protocols'
+
+# Extra options required by sphinx_idf_theme
+project_slug = 'esp-idf' # >=5.0
+versions_url = 'https://github.com/espressif/esp-protocols/docs/docs_versions.js'
+
+idf_targets = ['esp32']
+languages = ['en', 'zh_CN']
diff --git a/components/asio/docs/en/conf.py b/components/asio/docs/en/conf.py
new file mode 100644
index 000000000..bba642268
--- /dev/null
+++ b/components/asio/docs/en/conf.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#
+# English Language RTD & Sphinx config file
+#
+# Uses ../conf_common.py for most non-language-specific settings.
+
+# Importing conf_common adds all the non-language-specific
+# parts to this conf module
+
+try:
+ from conf_common import * # noqa: F403,F401
+except ImportError:
+ import os
+ import sys
+ sys.path.insert(0, os.path.abspath('../'))
+ from conf_common import * # noqa: F403,F401
+
+# General information about the project.
+project = u'ESP-Protocols'
+copyright = u'2016 - 2022, Espressif Systems (Shanghai) Co., Ltd'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+language = 'en'
diff --git a/components/asio/docs/en/index.rst b/components/asio/docs/en/index.rst
new file mode 100644
index 000000000..56ff694f2
--- /dev/null
+++ b/components/asio/docs/en/index.rst
@@ -0,0 +1,42 @@
+ASIO port
+=========
+
+Overview
+--------
+Asio is a cross-platform C++ library, see https://think-async.com/Asio/. It provides a consistent asynchronous model using a modern C++ approach.
+
+
+ASIO documentation
+^^^^^^^^^^^^^^^^^^
+Please refer to the original asio documentation at https://think-async.com/Asio/Documentation.
+Asio also comes with a number of examples which could be find under Documentation/Examples on that web site.
+
+Supported features
+^^^^^^^^^^^^^^^^^^
+ESP platform port currently supports only network asynchronous socket operations; does not support serial port.
+SSL/TLS support is disabled by default and could be enabled in component configuration menu by choosing TLS library from
+
+- mbedTLS with OpenSSL translation layer (default option)
+- wolfSSL
+
+SSL support is very basic at this stage and it does include following features:
+
+- Verification callbacks
+- DH property files
+- Certificates/private keys file APIs
+
+Internal asio settings for ESP include
+
+- EXCEPTIONS are enabled in ASIO if enabled in menuconfig
+- TYPEID is enabled in ASIO if enabled in menuconfig
+
+Application Example
+-------------------
+ESP examples are based on standard asio :example:`examples <../examples>`:
+
+- :example:`udp_echo_server <../examples/udp_echo_server>`
+- :example:`tcp_echo_server <../examples/tcp_echo_server>`
+- :example:`asio_chat <../examples/asio_chat>`
+- :example:`ssl_client_server <../examples/ssl_client_server>`
+
+Please refer to the specific example README.md for details
diff --git a/components/asio/docs/generate_docs b/components/asio/docs/generate_docs
new file mode 100755
index 000000000..e60c1a8f9
--- /dev/null
+++ b/components/asio/docs/generate_docs
@@ -0,0 +1,27 @@
+build-docs --target esp32 --language en
+
+cp -rf _build/en/esp32/html .
+rm -rf _build __pycache__
+
+# Modifes some version and target fields of index.html
+echo "" >> html/index.html
+
diff --git a/components/asio/docs/zh_CN/conf.py b/components/asio/docs/zh_CN/conf.py
new file mode 100644
index 000000000..cc6fbc271
--- /dev/null
+++ b/components/asio/docs/zh_CN/conf.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# English Language RTD & Sphinx config file
+#
+# Uses ../conf_common.py for most non-language-specific settings.
+
+# Importing conf_common adds all the non-language-specific
+# parts to this conf module
+try:
+ from conf_common import * # noqa: F403,F401
+except ImportError:
+ import os
+ import sys
+ sys.path.insert(0, os.path.abspath('..'))
+ from conf_common import * # noqa: F403,F401
+
+import datetime
+
+current_year = datetime.datetime.now().year
+
+# General information about the project.
+project = u'ESP-IDF 编程指南'
+copyright = u'2016 - {} 乐鑫信息科技(上海)股份有限公司'.format(current_year)
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+language = 'zh_CN'
diff --git a/components/asio/docs/zh_CN/index.rst b/components/asio/docs/zh_CN/index.rst
new file mode 100644
index 000000000..d1afa231e
--- /dev/null
+++ b/components/asio/docs/zh_CN/index.rst
@@ -0,0 +1 @@
+.. include:: ../../../en/api-reference/protocols/asio.rst
\ No newline at end of file
diff --git a/components/asio/examples/asio_chat/CMakeLists.txt b/components/asio/examples/asio_chat/CMakeLists.txt
new file mode 100644
index 000000000..2d3c9ecbe
--- /dev/null
+++ b/components/asio/examples/asio_chat/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(asio_chat)
diff --git a/components/asio/examples/asio_chat/README.md b/components/asio/examples/asio_chat/README.md
new file mode 100644
index 000000000..2896e9ab7
--- /dev/null
+++ b/components/asio/examples/asio_chat/README.md
@@ -0,0 +1,65 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
+| ----------------- | ----- | -------- | -------- | -------- |
+
+
+# Asio chat client and server examples
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+The application aims to demonstrate a simple use of Asio library in different modes.
+In project settings it could be configured to run either a Asio chat server, a Asio chat client, or both.
+
+## How to use example
+
+The example is configured by default as an Asio chat client.
+
+Note that the example uses string representation of IP addresses and ports.
+
+You can find the upstream asio chat implementation [here] https://github.com/chriskohlhoff/asio/tree/master/asio/src/examples/cpp11/chat
+
+### Asio Client
+
+In the client mode, the example connects to the configured address, sends the message, which was inserted as an input in the terminal, and receives a response.
+
+### Asio Server
+
+In the server mode, Asio chat server with a specified port number is created and being polled till a connection request from the client arrives.
+Chat server echoes a message (received from any client) to all connected clients.
+
+## Configure the project
+
+```
+idf.py menuconfig
+```
+
+Set following parameters under Example Configuration Options:
+
+* Set `EXAMPLE_CHAT_SERVER` to use the example as an ASIO chat server
+ * Configure `EXAMPLE_CHAT_SERVER_BIND_PORT` to the port number.
+
+* Set `EXAMPLE_CHAT_CLIENT` to use the example as an ASIO chat client
+ * Configure `EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS` to a string representation of the address to connect the client to.
+ * Configure `EXAMPLE_CHAT_CLIENT_CONNECT_PORT` to the port number.
+
+* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more d etails.
+
+## Running the example in server mode
+
+- Configure the example according "Configure the project" section.
+- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
+- Wait for the board to connect to WiFi or Ethernet (note the IP address).
+- Connect to the server using multiple clients, for example using any option below.
+ - build and run asio chat client on your host machine
+ - run chat_client asio example on ESP platform
+ - since chat messages consists of ASCII size and message, it is possible to
+ netcat `nc IP PORT` and type for example ` 4ABC` to transmit 'ABC\n'
+
+## Running the example in client mode
+
+- Configure the example according "Configure the project" section.
+- Start chat server either on host machine or as another ESP device running chat_server example.
+- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
+- Wait for the board to connect to WiFi or Ethernet.
+- Receive and send messages to/from other clients on stdin/stdout via serial terminal.
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/components/asio/examples/asio_chat/example_test.py b/components/asio/examples/asio_chat/example_test.py
new file mode 100644
index 000000000..7dc5c7c2b
--- /dev/null
+++ b/components/asio/examples/asio_chat/example_test.py
@@ -0,0 +1,29 @@
+# This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+# Unless required by applicable law or agreed to in writing, this
+# software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+# CONDITIONS OF ANY KIND, either express or implied.
+
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function, unicode_literals
+
+import re
+
+import ttfw_idf
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
+def test_examples_asio_chat(env, _): # type: (ttfw_idf.TinyFW.Env, None) -> None
+ msg = 'asio-chat: received hi'
+ dut = env.get_dut('asio_chat', 'examples/protocols/asio/asio_chat')
+ # start the test and expect the client to receive back it's original data
+ dut.start_app()
+ dut.expect(re.compile(r'{}'.format('Waiting for input')), timeout=30)
+ dut.write(msg)
+ dut.write('exit')
+ dut.expect(re.compile(r'{}'.format(msg)), timeout=30)
+
+
+if __name__ == '__main__':
+ test_examples_asio_chat()
diff --git a/components/asio/examples/asio_chat/main/CMakeLists.txt b/components/asio/examples/asio_chat/main/CMakeLists.txt
new file mode 100644
index 000000000..8f1954423
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "asio_chat.cpp"
+ INCLUDE_DIRS ".")
diff --git a/components/asio/examples/asio_chat/main/Kconfig.projbuild b/components/asio/examples/asio_chat/main/Kconfig.projbuild
new file mode 100644
index 000000000..99f08cdbd
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/Kconfig.projbuild
@@ -0,0 +1,39 @@
+menu "Example Configuration"
+
+ config EXAMPLE_CHAT_SERVER
+ bool "Asio example chat server"
+ default n
+ help
+ This example will setup a chat server, binds it to the specified address
+ and starts listening.
+
+ if EXAMPLE_CHAT_SERVER
+ config EXAMPLE_CHAT_SERVER_BIND_PORT
+ string "Asio example server bind port"
+ default "3344"
+ help
+ Server listener's socket would be bound to this port.
+ endif
+
+ config EXAMPLE_CHAT_CLIENT
+ bool "Asio example chat client"
+ default y
+ help
+ This example will setup an asio chat client.
+ and sends the data.
+
+ if EXAMPLE_CHAT_CLIENT
+ config EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS
+ string "Client connection address"
+ default "192.168.0.1"
+ help
+ Client's socket would connect to this address/host.
+
+ config EXAMPLE_CHAT_CLIENT_CONNECT_PORT
+ string "Client connection port"
+ default "3344"
+ help
+ Client's connection port.
+ endif
+
+endmenu
diff --git a/components/asio/examples/asio_chat/main/asio_chat.cpp b/components/asio/examples/asio_chat/main/asio_chat.cpp
new file mode 100644
index 000000000..be2329d15
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/asio_chat.cpp
@@ -0,0 +1,119 @@
+/* ASIO chat server client example
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include "protocol_examples_common.h"
+#include "esp_log.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include "server.hpp"
+#include "client.hpp"
+#include
+#include
+
+using asio::ip::tcp;
+
+static const char *TAG = "asio-chat";
+
+// This variable is necessary for `python test` execution, it provides synchronisation between server/client(as server should be started before client)
+std::mutex server_ready;
+
+#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
+static void get_string(char *line, size_t size)
+{
+ int count = 0;
+ while (count < size) {
+ int c = fgetc(stdin);
+ if (c == '\n') {
+ line[count] = '\0';
+ break;
+ } else if (c > 0 && c < 127) {
+ line[count] = c;
+ ++count;
+ }
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ }
+}
+
+void start_client(void)
+{
+ const std::string port(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_PORT);
+ const std::string name(CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS);
+ asio::io_context io_context;
+ char line[128];
+
+ tcp::resolver resolver(io_context);
+ auto endpoints = resolver.resolve(name, port);
+ chat_client c(io_context, endpoints);
+#ifdef CONFIG_EXAMPLE_CHAT_SERVER
+ std::lock_guard guard(server_ready);
+#endif
+ std::thread t([&io_context]() { try {
+ io_context.run();
+ } catch (const std::exception &e) {
+ ESP_LOGE(TAG, "Exception occured during client thread execution %s", e.what());
+ }
+ catch (...) {
+ ESP_LOGE(TAG, "Unknown exception");
+ }});
+ do {
+ ESP_LOGI(TAG, "CLIENT: Waiting for input");
+ get_string(line, sizeof(line));
+
+ chat_message msg;
+ msg.body_length(std::strlen(line));
+ std::memcpy(msg.body(), line, msg.body_length());
+ msg.encode_header();
+ c.write(msg);
+ sleep(1);
+ } while (strcmp(line, "exit") != 0);
+
+ c.close();
+ t.join();
+}
+#endif // CONFIG_EXAMPLE_CHAT_CLIENT
+
+extern "C" void app_main(void)
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ esp_netif_init();
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+ try {
+#ifdef CONFIG_EXAMPLE_CHAT_SERVER
+ asio::io_context io_context;
+ chat_server server(io_context, tcp::endpoint(tcp::v4(), std::atoi(CONFIG_EXAMPLE_CHAT_SERVER_BIND_PORT)));
+ std::thread t = std::thread([&io_context]() { // Chat server starting here
+ try {
+ io_context.run();
+ } catch (const std::exception &e) {
+ ESP_LOGE(TAG, "Exception occured during server thread execution %s", e.what());
+ }
+ catch (...) {
+ ESP_LOGE(TAG, "Unknown exception");
+ }});;
+#endif
+#ifdef CONFIG_EXAMPLE_CHAT_CLIENT
+ start_client();
+#endif
+#ifdef CONFIG_EXAMPLE_CHAT_SERVER
+ t.join();
+#endif
+ } catch (const std::exception &e) {
+ ESP_LOGE(TAG, "Exception occured during run %s", e.what());
+ } catch (...) {
+ ESP_LOGE(TAG, "Unknown exception");
+ }
+ ESP_ERROR_CHECK(example_disconnect());
+}
diff --git a/components/asio/examples/asio_chat/main/chat_message.hpp b/components/asio/examples/asio_chat/main/chat_message.hpp
new file mode 100644
index 000000000..a6ffc83b8
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/chat_message.hpp
@@ -0,0 +1,91 @@
+//
+// chat_message.hpp
+// ~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef CHAT_MESSAGE_HPP
+#define CHAT_MESSAGE_HPP
+
+#include
+#include
+#include
+
+class chat_message
+{
+public:
+ static constexpr std::size_t header_length = 4;
+ static constexpr std::size_t max_body_length = 512;
+
+ chat_message()
+ : body_length_(0)
+ {
+ }
+
+ const char* data() const
+ {
+ return data_;
+ }
+
+ char* data()
+ {
+ return data_;
+ }
+
+ std::size_t length() const
+ {
+ return header_length + body_length_;
+ }
+
+ const char* body() const
+ {
+ return data_ + header_length;
+ }
+
+ char* body()
+ {
+ return data_ + header_length;
+ }
+
+ std::size_t body_length() const
+ {
+ return body_length_;
+ }
+
+ void body_length(std::size_t new_length)
+ {
+ body_length_ = new_length;
+ if (body_length_ > max_body_length)
+ body_length_ = max_body_length;
+ }
+
+ bool decode_header()
+ {
+ char header[header_length + 1] = "";
+ std::strncat(header, data_, header_length);
+ body_length_ = std::atoi(header);
+ if (body_length_ > max_body_length)
+ {
+ body_length_ = 0;
+ return false;
+ }
+ return true;
+ }
+
+ void encode_header()
+ {
+ char header[header_length + 1] = "";
+ std::sprintf(header, "%4d", static_cast(body_length_));
+ std::memcpy(data_, header, header_length);
+ }
+
+private:
+ char data_[header_length + max_body_length];
+ std::size_t body_length_;
+};
+
+#endif // CHAT_MESSAGE_HPP
diff --git a/components/asio/examples/asio_chat/main/client.hpp b/components/asio/examples/asio_chat/main/client.hpp
new file mode 100644
index 000000000..09baece98
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/client.hpp
@@ -0,0 +1,126 @@
+//
+// client.hpp
+// ~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef CHAT_CLIENT_HPP
+#define CHAT_CLIENT_HPP
+
+#include
+#include "asio.hpp"
+#include "chat_message.hpp"
+
+typedef std::deque chat_message_queue;
+
+class chat_client
+{
+public:
+ chat_client(asio::io_context& io_context,
+ const asio::ip::tcp::resolver::results_type& endpoints)
+ : io_context_(io_context),
+ socket_(io_context)
+ {
+ do_connect(endpoints);
+ }
+
+ void write(const chat_message& msg)
+ {
+ asio::post(io_context_,
+ [this, msg]()
+ {
+ bool write_in_progress = !write_msgs_.empty();
+ write_msgs_.push_back(msg);
+ if (!write_in_progress)
+ {
+ do_write();
+ }
+ });
+ }
+
+ void close()
+ {
+ asio::post(io_context_, [this]() { socket_.close(); });
+ }
+
+private:
+void do_connect(const asio::ip::tcp::resolver::results_type& endpoints)
+ {
+ asio::async_connect(socket_, endpoints,
+ [this](std::error_code ec, asio::ip::tcp::endpoint)
+ {
+ if (!ec)
+ {
+ do_read_header();
+ }
+ });
+ }
+
+ void do_read_header()
+ {
+ asio::async_read(socket_,
+ asio::buffer(read_msg_.data(), chat_message::header_length),
+ [this](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec && read_msg_.decode_header())
+ {
+ do_read_body();
+ }
+ else
+ {
+ socket_.close();
+ }
+ });
+ }
+
+ void do_read_body()
+ {
+ asio::async_read(socket_,
+ asio::buffer(read_msg_.body(), read_msg_.body_length()),
+ [this](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec)
+ {
+ do_read_header();
+ }
+ else
+ {
+ socket_.close();
+ }
+ });
+ }
+
+ void do_write()
+ {
+ asio::async_write(socket_,
+ asio::buffer(write_msgs_.front().data(),
+ write_msgs_.front().length()),
+ [this](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec)
+ {
+ write_msgs_.pop_front();
+ if (!write_msgs_.empty())
+ {
+ do_write();
+ }
+ }
+ else
+ {
+ socket_.close();
+ }
+ });
+ }
+
+private:
+ asio::io_context& io_context_;
+ asio::ip::tcp::socket socket_;
+ chat_message read_msg_;
+ chat_message_queue write_msgs_;
+};
+
+#endif // CHAT_CLIENT_HPP
diff --git a/components/asio/examples/asio_chat/main/server.hpp b/components/asio/examples/asio_chat/main/server.hpp
new file mode 100644
index 000000000..450e255a4
--- /dev/null
+++ b/components/asio/examples/asio_chat/main/server.hpp
@@ -0,0 +1,202 @@
+//
+// server.hpp
+// ~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef CHAT_SERVER_HPP
+#define CHAT_SERVER_HPP
+
+#include
+#include
+#include
+#include
+#include "asio.hpp"
+#include "chat_message.hpp"
+
+//----------------------------------------------------------------------
+
+typedef std::deque chat_message_queue;
+
+extern std::mutex server_ready;
+
+//----------------------------------------------------------------------
+
+
+class chat_participant
+{
+public:
+ virtual ~chat_participant() {}
+ virtual void deliver(const chat_message& msg) = 0;
+};
+
+typedef std::shared_ptr chat_participant_ptr;
+
+//----------------------------------------------------------------------
+
+class chat_room
+{
+public:
+ void join(chat_participant_ptr participant)
+ {
+ participants_.insert(participant);
+ for (auto msg: recent_msgs_)
+ participant->deliver(msg);
+ }
+
+ void leave(chat_participant_ptr participant)
+ {
+ participants_.erase(participant);
+ }
+
+ void deliver(const chat_message& msg)
+ {
+ recent_msgs_.push_back(msg);
+ while (recent_msgs_.size() > max_recent_msgs)
+ recent_msgs_.pop_front();
+
+ for (auto participant: participants_)
+ participant->deliver(msg);
+ }
+
+private:
+ std::set participants_;
+ enum { max_recent_msgs = 100 };
+ chat_message_queue recent_msgs_;
+};
+
+//----------------------------------------------------------------------
+
+
+class chat_session
+ : public chat_participant,
+ public std::enable_shared_from_this
+{
+public:
+ chat_session(asio::ip::tcp::socket socket, chat_room& room)
+ : socket_(std::move(socket)),
+ room_(room)
+ {
+ }
+
+ void start()
+ {
+ room_.join(shared_from_this());
+ do_read_header();
+ }
+
+ void deliver(const chat_message& msg)
+ {
+ bool write_in_progress = !write_msgs_.empty();
+ write_msgs_.push_back(msg);
+ if (!write_in_progress)
+ {
+ do_write();
+ }
+ }
+
+private:
+ void do_read_header()
+ {
+ auto self(shared_from_this());
+ asio::async_read(socket_,
+ asio::buffer(read_msg_.data(), chat_message::header_length),
+ [this, self](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec && read_msg_.decode_header())
+ {
+ do_read_body();
+ }
+ else
+ {
+ room_.leave(shared_from_this());
+ }
+ });
+ }
+
+ void do_read_body()
+ {
+ auto self(shared_from_this());
+ asio::async_read(socket_,
+ asio::buffer(read_msg_.body(), read_msg_.body_length()),
+ [this, self](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec)
+ {
+ ESP_LOGD("asio-chat:", "%s", read_msg_.body());
+ room_.deliver(read_msg_);
+ do_read_header();
+ }
+ else
+ {
+ room_.leave(shared_from_this());
+ }
+ });
+ }
+
+ void do_write()
+ {
+ auto self(shared_from_this());
+ asio::async_write(socket_,
+ asio::buffer(write_msgs_.front().data(),
+ write_msgs_.front().length()),
+ [this, self](std::error_code ec, std::size_t /*length*/)
+ {
+ if (!ec)
+ {
+ write_msgs_.pop_front();
+ if (!write_msgs_.empty())
+ {
+ do_write();
+ }
+ }
+ else
+ {
+ room_.leave(shared_from_this());
+ }
+ });
+ }
+
+ asio::ip::tcp::socket socket_;
+ chat_room& room_;
+ chat_message read_msg_;
+ chat_message_queue write_msgs_;
+ };
+
+//----------------------------------------------------------------------
+
+class chat_server
+{
+public:
+ chat_server(asio::io_context& io_context,
+ const asio::ip::tcp::endpoint& endpoint)
+ : acceptor_(io_context, endpoint)
+ {
+ do_accept();
+ }
+
+private:
+ void do_accept()
+ {
+ std::lock_guard guard(server_ready);
+ acceptor_.async_accept(
+ [this](std::error_code ec, asio::ip::tcp::socket socket)
+ {
+ if (!ec)
+ {
+ std::make_shared(std::move(socket), room_)->start();
+ }
+
+ do_accept();
+ });
+ }
+
+ asio::ip::tcp::acceptor acceptor_;
+ chat_room room_;
+};
+
+#endif // CHAT_SERVER_HPP
diff --git a/components/asio/examples/asio_chat/sdkconfig.ci b/components/asio/examples/asio_chat/sdkconfig.ci
new file mode 100644
index 000000000..4829992d0
--- /dev/null
+++ b/components/asio/examples/asio_chat/sdkconfig.ci
@@ -0,0 +1,6 @@
+CONFIG_EXAMPLE_CONNECT_WIFI=n
+CONFIG_EXAMPLE_CONNECT_ETHERNET=n
+CONFIG_EXAMPLE_CHAT_CLIENT=y
+CONFIG_EXAMPLE_CHAT_SERVER=y
+CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
+CONFIG_EXAMPLE_CHAT_CLIENT_CONNECT_ADDRESS="localhost"
diff --git a/components/asio/examples/asio_chat/sdkconfig.defaults b/components/asio/examples/asio_chat/sdkconfig.defaults
new file mode 100644
index 000000000..3a66e33fd
--- /dev/null
+++ b/components/asio/examples/asio_chat/sdkconfig.defaults
@@ -0,0 +1,2 @@
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
diff --git a/components/asio/examples/async_request/CMakeLists.txt b/components/asio/examples/async_request/CMakeLists.txt
new file mode 100644
index 000000000..ff659707e
--- /dev/null
+++ b/components/asio/examples/async_request/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(async_http_request)
diff --git a/components/asio/examples/async_request/README.md b/components/asio/examples/async_request/README.md
new file mode 100644
index 000000000..c2b114511
--- /dev/null
+++ b/components/asio/examples/async_request/README.md
@@ -0,0 +1,52 @@
+| Supported Targets | ESP32 |
+| ----------------- | ----- |
+
+# Async request using ASIO
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+The application aims to show how to compose async operations using ASIO to build network protocols and operations.
+
+# Configure and Building example
+
+This example doesn't require any configuration, just build it with
+
+```
+idf.py build
+```
+
+# Async operations composition and automatic lifetime control
+
+On this example we compose the operation by starting the next step in the chain inside the completion handler of the
+previous operation. Also we pass the `Connection` class itself as the parameter of its final handler to be owned by
+the following operation. This is possible due to the control of lifetime by the usage of `std::shared_ptr`.
+
+The control of lifetime of the class, done by `std::shared_ptr` usage, guarantee that the data will be available for
+async operations until it's not needed any more. This makes necessary that all of the async operation class must start
+its lifetime as a `std::shared_ptr` due to the usage of `std::enable_shared_from_this`.
+
+
+ User creates a shared_ptr──┐
+ of AddressResolution and │
+ ask for resolve. │
+ The handler for the ┌▼─────────────────────┐
+ complete operation is sent│ AddressResolution │ In the completion of resolve a connection is created.
+ └─────────────────┬────┘ AddressResolution is automaticly destroyed since it's
+ │ no longer needed
+ ┌─▼────────────────────────────────────┐
+ │ Connection │
+ └──────┬───────────────────────────────┘
+ Http::Session is created once we have a Connection. │
+ Connection is passed to Http::Session that holds it │
+ avoiding it's destruction. │
+ ┌─▼───────────────────────────────┐
+ │ Http::Session │
+ └────────┬────────────────────────┘
+ After the HTTP request is │
+ sent the completion handler │
+ is called. │
+ └────►Completion Handler()
+
+
+The previous diagram shows the process and the life span of each of the tasks in this examples. At each stage the
+object responsible for the last action inject itself to the completion handler of the next stage for reuse.
diff --git a/components/asio/examples/async_request/main/CMakeLists.txt b/components/asio/examples/async_request/main/CMakeLists.txt
new file mode 100644
index 000000000..018c22a0a
--- /dev/null
+++ b/components/asio/examples/async_request/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "async_http_request.cpp"
+ INCLUDE_DIRS ".")
diff --git a/components/asio/examples/async_request/main/async_http_request.cpp b/components/asio/examples/async_request/main/async_http_request.cpp
new file mode 100644
index 000000000..3f2df210b
--- /dev/null
+++ b/components/asio/examples/async_request/main/async_http_request.cpp
@@ -0,0 +1,369 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ *
+ * ASIO HTTP request example
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_log.h"
+#include "nvs_flash.h"
+#include "esp_event.h"
+#include "protocol_examples_common.h"
+
+constexpr auto TAG = "async_request";
+using asio::ip::tcp;
+
+namespace {
+
+void esp_init()
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ esp_log_level_set("async_request", ESP_LOG_DEBUG);
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+}
+
+/**
+ * @brief Simple class to add the resolver to a chain of actions
+ *
+ */
+class AddressResolution : public std::enable_shared_from_this {
+public:
+ explicit AddressResolution(asio::io_context &context) : ctx(context), resolver(ctx) {}
+
+ /**
+ * @brief Initiator function for the address resolution
+ *
+ * @tparam CompletionToken callable responsible to use the results.
+ *
+ * @param host Host address
+ * @param port Port for the target, must be number due to a limitation on lwip.
+ */
+ template
+ void resolve(const std::string &host, const std::string &port, CompletionToken &&completion_handler)
+ {
+ auto self(shared_from_this());
+ resolver.async_resolve(host, port, [self, completion_handler](const asio::error_code & error, tcp::resolver::results_type results) {
+ if (error) {
+ ESP_LOGE(TAG, "Failed to resolve: %s", error.message().c_str());
+ return;
+ }
+ completion_handler(self, results);
+ });
+ }
+
+private:
+ asio::io_context &ctx;
+ tcp::resolver resolver;
+
+};
+
+/**
+ * @brief Connection class
+ *
+ * The lowest level dependency on our asynchronous task, Connection provide an interface to TCP sockets.
+ * A similar class could be provided for a TLS connection.
+ *
+ * @note: All read and write operations are written on an explicit strand, even though an implicit strand
+ * occurs in this example since we run the io context in a single task.
+ *
+ */
+class Connection : public std::enable_shared_from_this {
+public:
+ explicit Connection(asio::io_context &context) : ctx(context), strand(context), socket(ctx) {}
+
+ /**
+ * @brief Start the connection
+ *
+ * Async operation to start a connection. As the final act of the process the Connection class pass a
+ * std::shared_ptr of itself to the completion_handler.
+ * Since it uses std::shared_ptr as an automatic control of its lifetime this class must be created
+ * through a std::make_shared call.
+ *
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ * @param host host address
+ * @param port port number - due to a limitation on lwip implementation this should be the number not the
+ * service name typically seen in ASIO examples.
+ *
+ * @note The class could be modified to store the completion handler, as a member variable, instead of
+ * pass it along asynchronous calls to allow the process to run again completely.
+ *
+ */
+ template
+ void start(tcp::resolver::results_type results, CompletionToken &&completion_handler)
+ {
+ connect(results, completion_handler);
+ }
+
+ /**
+ * @brief Start an async write on the socket
+ *
+ * @tparam data
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ *
+ */
+ template
+ void write_async(const DataType &data, CompletionToken &&completion_handler)
+ {
+ asio::async_write(socket, data, asio::bind_executor(strand, completion_handler));
+ }
+
+ /**
+ * @brief Start an async read on the socket
+ *
+ * @tparam data
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ *
+ */
+ template
+ void read_async(DataBuffer &&in_data, CompletionToken &&completion_handler)
+ {
+ asio::async_read(socket, in_data, asio::bind_executor(strand, completion_handler));
+ }
+
+private:
+
+ template
+ void connect(tcp::resolver::results_type results, CompletionToken &&completion_handler)
+ {
+ auto self(shared_from_this());
+ asio::async_connect(socket, results, [self, completion_handler](const asio::error_code & error, [[maybe_unused]] const tcp::endpoint & endpoint) {
+ if (error) {
+ ESP_LOGE(TAG, "Failed to connect: %s", error.message().c_str());
+ return;
+ }
+ completion_handler(self);
+ });
+ }
+ asio::io_context &ctx;
+ asio::io_context::strand strand;
+ tcp::socket socket;
+};
+
+} // namespace
+namespace Http {
+enum class Method { GET };
+
+/**
+ * @brief Simple HTTP request class
+ *
+ * The user needs to write the request information direct to header and body fields.
+ *
+ * Only GET verb is provided.
+ *
+ */
+class Request {
+public:
+ Request(Method method, std::string host, std::string port, const std::string &target) : host_data(std::move(host)), port_data(std::move(port))
+ {
+ header_data.append("GET ");
+ header_data.append(target);
+ header_data.append(" HTTP/1.1");
+ header_data.append("\r\n");
+ header_data.append("Host: ");
+ header_data.append(host_data);
+ header_data.append("\r\n");
+ header_data.append("\r\n");
+ };
+
+ void set_header_field(std::string const &field)
+ {
+ header_data.append(field);
+ }
+
+ void append_to_body(std::string const &data)
+ {
+ body_data.append(data);
+ };
+
+ const std::string &host() const
+ {
+ return host_data;
+ }
+
+ const std::string &service_port() const
+ {
+ return port_data;
+ }
+
+ const std::string &header() const
+ {
+ return header_data;
+ }
+
+ const std::string &body() const
+ {
+ return body_data;
+ }
+
+private:
+ std::string host_data;
+ std::string port_data;
+ std::string header_data;
+ std::string body_data;
+};
+
+/**
+ * @brief Simple HTTP response class
+ *
+ * The response is built from received data and only parsed to split header and body.
+ *
+ * A copy of the received data is kept.
+ *
+ */
+struct Response {
+ /**
+ * @brief Construct a response from a contiguous buffer.
+ *
+ * Simple http parsing.
+ *
+ */
+ template
+ explicit Response(DataIt data, size_t size)
+ {
+ raw_response = std::string(data, size);
+
+ auto header_last = raw_response.find("\r\n\r\n");
+ if (header_last != std::string::npos) {
+ header = raw_response.substr(0, header_last);
+ }
+ body = raw_response.substr(header_last + 3);
+ }
+ /**
+ * @brief Print response content.
+ */
+ void print()
+ {
+ ESP_LOGI(TAG, "Header :\n %s", header.c_str());
+ ESP_LOGI(TAG, "Body : \n %s", body.c_str());
+ }
+
+ std::string raw_response;
+ std::string header;
+ std::string body;
+};
+
+/** @brief HTTP Session
+ *
+ * Session class to handle HTTP protocol implementation.
+ *
+ */
+class Session : public std::enable_shared_from_this {
+public:
+ explicit Session(std::shared_ptr connection_in) : connection(std::move(connection_in))
+ {
+ }
+
+ template
+ void send_request(const Request &request, CompletionToken &&completion_handler)
+ {
+ auto self = shared_from_this();
+ send_data = { asio::buffer(request.header()), asio::buffer(request.body()) };
+ connection->write_async(send_data, [self, &completion_handler](std::error_code error, std::size_t bytes_transfered) {
+ if (error) {
+ ESP_LOGE(TAG, "Request write error: %s", error.message().c_str());
+ return;
+ }
+ ESP_LOGD(TAG, "Bytes Transfered: %d", bytes_transfered);
+ self->get_response(completion_handler);
+ });
+ }
+
+private:
+ template
+ void get_response(CompletionToken &&completion_handler)
+ {
+ auto self = shared_from_this();
+ connection->read_async(asio::buffer(receive_buffer), [self, &completion_handler](std::error_code error, std::size_t bytes_received) {
+ if (error and error.value() != asio::error::eof) {
+ return;
+ }
+ ESP_LOGD(TAG, "Bytes Received: %d", bytes_received);
+ if (bytes_received == 0) {
+ return;
+ }
+ Response response(std::begin(self->receive_buffer), bytes_received);
+
+ completion_handler(self, response);
+ });
+ }
+ /*
+ * For this example we assumed 2048 to be enough for the receive_buffer
+ */
+ std::array receive_buffer;
+ /*
+ * The hardcoded 2 below is related to the type we receive the data to send. We gather the parts from Request, header
+ * and body, to send avoiding the copy.
+ */
+ std::array send_data;
+ std::shared_ptr connection;
+};
+
+/** @brief Execute a fully async HTTP request
+ *
+ * @tparam completion_handler
+ * @param ctx io context
+ * @param request
+ *
+ * @note : We build this function as a simpler interface to compose the operations of connecting to
+ * the address and running the HTTP session. The Http::Session class is injected to the completion handler
+ * for further use.
+ */
+template
+void request_async(asio::io_context &context, const Request &request, CompletionToken &&completion_handler)
+{
+ /*
+ * The first step is to resolve the address we want to connect to.
+ * The AddressResolution itself is injected to the completion handler.
+ *
+ * This shared_ptr is destroyed by the end of the scope. Pay attention that this is a non blocking function
+ * the lifetime of the object is extended by the resolve call
+ */
+ std::make_shared(context)->resolve(request.host(), request.service_port(),
+ [&context, &request, completion_handler](std::shared_ptr resolver, tcp::resolver::results_type results) {
+ /* After resolution we create a Connection.
+ * The completion handler gets a shared_ptr to receive the connection, once the
+ * connection process is complete.
+ */
+ std::make_shared(context)->start(results,
+ [&request, completion_handler](std::shared_ptr connection) {
+ // Now we create a HTTP::Session and inject the necessary connection.
+ std::make_shared(connection)->send_request(request, completion_handler);
+ });
+ });
+}
+}// namespace Http
+
+extern "C" void app_main(void)
+{
+ // Basic initialization of ESP system
+ esp_init();
+
+ asio::io_context io_context;
+ Http::Request request(Http::Method::GET, "www.httpbin.org", "80", "/get");
+ Http::request_async(io_context, request, [](std::shared_ptr session, Http::Response response) {
+ /*
+ * We only print the response here but could reuse session for other requests.
+ */
+ response.print();
+ });
+
+ // io_context.run will block until all the tasks on the context are done.
+ io_context.run();
+ ESP_LOGI(TAG, "Context run done");
+
+ ESP_ERROR_CHECK(example_disconnect());
+}
diff --git a/components/asio/examples/socks4/CMakeLists.txt b/components/asio/examples/socks4/CMakeLists.txt
new file mode 100644
index 000000000..f1ffe9d12
--- /dev/null
+++ b/components/asio/examples/socks4/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(asio_sock4)
diff --git a/components/asio/examples/socks4/README.md b/components/asio/examples/socks4/README.md
new file mode 100644
index 000000000..f9cc88b46
--- /dev/null
+++ b/components/asio/examples/socks4/README.md
@@ -0,0 +1,73 @@
+| Supported Targets | ESP32 | ESP32-S2 |
+| ----------------- | ----- | ----- |
+
+# Async request using ASIO
+
+(See the README.md file in the upper level 'examples' directory for more information about examples.)
+
+The application aims to show how to connect to a Socks4 proxy using async operations with ASIO. The SOCKS protocol is
+briefly described by the diagram below.
+
+ ┌──────┐ ┌─────┐ ┌──────┐
+ │Client│ │Proxy│ │Target│
+ └──┬───┘ └──┬──┘ └──┬───┘
+ │ │ │
+ │ ╔═╧══════════════╗ │
+══════════════════════╪════════════════════════╣ Initialization ╠═══╪════════════════════════════════════════════
+ │ ╚═╤══════════════╝ │
+ │ │ │
+ ╔══════════════════╗│ │ │
+ ║We establish a ░║│ Socket Connection │ │
+ ║TCP connection ║│ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ > │
+ ║and get a socket ║│ │ │
+ ╚══════════════════╝│ │ │
+ │ │ │
+ │ ╔═╧══════════════╗ │
+══════════════════════╪════════════════════════╣ Socks Protocol ╠═══╪════════════════════════════════════════════
+ │ ╚═╤══════════════╝ │
+ │ │ │
+ │ Client Connection Request│ │
+ │ ─────────────────────────> │
+ │ │ │
+ │ │ │
+ │ ╔════════════╪═══════╤══════════╪════════════════════════════════╗
+ │ ║ TARGET CONNECTION │ │ ║
+ │ ╟────────────────────┘ │ ╔═══════════════════╗ ║
+ │ ║ │ Socket Connection│ ║Proxy establishes ░║ ║
+ │ ║ │ <─ ─ ─ ─ ─ ─ ─ ─ > ║ TCPconnection ║ ║
+ │ ║ │ │ ║ with target host ║ ║
+ │ ╚════════════╪══════════════════╪══╚═══════════════════╝═════════╝
+ │ │ │
+ │ Response packet │ │
+ │ <───────────────────────── │
+ │ │ │
+ │ │ │
+ │ │ ╔═══════╗ │
+══════════════════════╪══════════════════════════╪══╣ Usage ╠═══════╪════════════════════════════════════════════
+ │ │ ╚═══════╝ │
+ │ │ │
+ ╔═════════════════╗│ │ │
+ ║Client uses the ░║│ │ │
+ ║ socket opened ║│ │ │
+ ║ with proxy ║│ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─>
+ ║to communicate ║│ │ │
+ ║ ║│ │ │
+ ╚═════════════════╝┴───┐ ┌──┴──┐ ┌──┴───┐
+ │Client│ │Proxy│ │Target│
+ └──────┘ └─────┘ └──────┘
+
+
+# Configure and Building example
+
+This example requires the proxy address to be configured. You can do this using the menuconfig option.
+Proxy address and port must be configured in order for this example to work.
+
+If using Linux ssh can be used as a proxy for testing.
+
+```
+ssh -N -v -D 0.0.0.0:1080 localhost
+```
+# Async operations composition and automatic lifetime control
+
+For documentation about the structure of this example look into [async\_request README](../async_request/README.md).
+
diff --git a/components/asio/examples/socks4/main/CMakeLists.txt b/components/asio/examples/socks4/main/CMakeLists.txt
new file mode 100644
index 000000000..517ab52d9
--- /dev/null
+++ b/components/asio/examples/socks4/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "socks4.cpp"
+ INCLUDE_DIRS ".")
diff --git a/components/asio/examples/socks4/main/Kconfig.projbuild b/components/asio/examples/socks4/main/Kconfig.projbuild
new file mode 100644
index 000000000..69dd024cd
--- /dev/null
+++ b/components/asio/examples/socks4/main/Kconfig.projbuild
@@ -0,0 +1,16 @@
+menu "Example Configuration"
+
+ config EXAMPLE_PROXY_ADDRESS
+ string "Proxy address"
+ default "myproxy"
+ help
+ Address of the proxy to be used.
+
+ config EXAMPLE_PROXY_SERVICE
+ string "Proxy Service Type"
+ default "myport"
+ help
+ Service type. Due to a limitation of lwip, must
+ be the port number e.g. "1080".
+
+endmenu
diff --git a/components/asio/examples/socks4/main/socks4.cpp b/components/asio/examples/socks4/main/socks4.cpp
new file mode 100644
index 000000000..5bd0011ed
--- /dev/null
+++ b/components/asio/examples/socks4/main/socks4.cpp
@@ -0,0 +1,393 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: CC0-1.0
+ *
+ *
+ * ASIO Socks4 example
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "esp_log.h"
+#include "socks4.hpp"
+#include "nvs_flash.h"
+#include "esp_event.h"
+#include "protocol_examples_common.h"
+
+constexpr auto TAG = "asio_socks4";
+using asio::ip::tcp;
+
+namespace {
+
+void esp_init()
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+ esp_log_level_set("async_request", ESP_LOG_DEBUG);
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+}
+
+/**
+ * @brief Simple class to add the resolver to a chain of actions
+ *
+ */
+class AddressResolution : public std::enable_shared_from_this {
+public:
+ explicit AddressResolution(asio::io_context &context) : ctx(context), resolver(ctx) {}
+
+ /**
+ * @brief Initiator function for the address resolution
+ *
+ * @tparam CompletionToken callable responsible to use the results.
+ *
+ * @param host Host address
+ * @param port Port for the target, must be number due to a limitation on lwip.
+ */
+ template
+ void resolve(const std::string &host, const std::string &port, CompletionToken &&completion_handler)
+ {
+ auto self(shared_from_this());
+ resolver.async_resolve(host, port, [self, completion_handler](const asio::error_code & error, tcp::resolver::results_type results) {
+ if (error) {
+ ESP_LOGE(TAG, "Failed to resolve: %s", error.message().c_str());
+ return;
+ }
+ completion_handler(self, results);
+ });
+ }
+
+private:
+ asio::io_context &ctx;
+ tcp::resolver resolver;
+
+};
+
+/**
+ * @brief Connection class
+ *
+ * The lowest level dependency on our asynchronous task, Connection provide an interface to TCP sockets.
+ * A similar class could be provided for a TLS connection.
+ *
+ * @note: All read and write operations are written on an explicit strand, even though an implicit strand
+ * occurs in this example since we run the io context in a single task.
+ *
+ */
+class Connection : public std::enable_shared_from_this {
+public:
+ explicit Connection(asio::io_context &context) : ctx(context), strand(context), socket(ctx) {}
+
+ /**
+ * @brief Start the connection
+ *
+ * Async operation to start a connection. As the final act of the process the Connection class pass a
+ * std::shared_ptr of itself to the completion_handler.
+ * Since it uses std::shared_ptr as an automatic control of its lifetime this class must be created
+ * through a std::make_shared call.
+ *
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ * @param host host address
+ * @param port port number - due to a limitation on lwip implementation this should be the number not the
+ * service name typically seen in ASIO examples.
+ *
+ * @note The class could be modified to store the completion handler, as a member variable, instead of
+ * pass it along asynchronous calls to allow the process to run again completely.
+ *
+ */
+ template
+ void start(tcp::resolver::results_type results, CompletionToken &&completion_handler)
+ {
+ connect(results, completion_handler);
+ }
+
+ /**
+ * @brief Start an async write on the socket
+ *
+ * @tparam data
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ *
+ */
+ template
+ void write_async(const DataType &data, CompletionToken &&completion_handler)
+ {
+ asio::async_write(socket, data, asio::bind_executor(strand, completion_handler));
+ }
+
+ /**
+ * @brief Start an async read on the socket
+ *
+ * @tparam data
+ * @tparam completion_handler A callable to act as the final handler for the process.
+ *
+ */
+ template
+ void read_async(DataBuffer &&in_data, CompletionToken &&completion_handler)
+ {
+ asio::async_read(socket, in_data, asio::bind_executor(strand, completion_handler));
+ }
+
+private:
+
+ template
+ void connect(tcp::resolver::results_type results, CompletionToken &&completion_handler)
+ {
+ auto self(shared_from_this());
+ asio::async_connect(socket, results, [self, completion_handler](const asio::error_code & error, [[maybe_unused]] const tcp::endpoint & endpoint) {
+ if (error) {
+ ESP_LOGE(TAG, "Failed to connect: %s", error.message().c_str());
+ return;
+ }
+ completion_handler(self);
+ });
+ }
+ asio::io_context &ctx;
+ asio::io_context::strand strand;
+ tcp::socket socket;
+};
+
+}
+
+namespace Socks {
+
+struct ConnectionData {
+ ConnectionData(socks4::request::command_type cmd, const asio::ip::tcp::endpoint &endpoint,
+ const std::string &user_id) : request(cmd, endpoint, user_id) {};
+ socks4::request request;
+ socks4::reply reply;
+};
+
+template
+void async_connect(asio::io_context &context, std::string proxy, std::string proxy_port, std::string host, std::string port, CompletionToken &&completion_handler)
+{
+ /*
+ * The first step is to resolve the address of the proxy we want to connect to.
+ * The AddressResolution itself is injected to the completion handler.
+ */
+ // Resolve proxy
+ std::make_shared(context)->resolve(proxy, proxy_port,
+ [&context, host, port, completion_handler](std::shared_ptr resolver, tcp::resolver::results_type proxy_resolution) {
+ // We also need to resolve the target host address
+ resolver->resolve(host, port, [&context, proxy_resolution, completion_handler](std::shared_ptr resolver, tcp::resolver::results_type host_resolution) {
+ // Make connection with the proxy
+ ESP_LOGI(TAG, "Startig Proxy Connection");
+ std::make_shared(context)->start(proxy_resolution,
+ [resolver, host_resolution, completion_handler](std::shared_ptr connection) {
+ auto connect_data = std::make_shared(socks4::request::connect, *host_resolution, "");
+ ESP_LOGI(TAG, "Sending Request to proxy for host connection.");
+ connection->write_async(connect_data->request.buffers(), [connection, connect_data, completion_handler](std::error_code error, std::size_t bytes_received) {
+ if (error) {
+ ESP_LOGE(TAG, "Proxy request write error: %s", error.message().c_str());
+ return;
+ }
+ connection->read_async(connect_data->reply.buffers(), [connection, connect_data, completion_handler](std::error_code error, std::size_t bytes_received) {
+ if (error) {
+
+ ESP_LOGE(TAG, "Proxy response read error: %s", error.message().c_str());
+ return;
+ }
+ if (!connect_data->reply.success()) {
+ ESP_LOGE(TAG, "Proxy error: %#x", connect_data->reply.status());
+ }
+ completion_handler(connection);
+
+ });
+
+ });
+
+ });
+
+ });
+ });
+
+}
+} // namespace Socks
+
+namespace Http {
+enum class Method { GET };
+
+/**
+ * @brief Simple HTTP request class
+ *
+ * The user needs to write the request information direct to header and body fields.
+ *
+ * Only GET verb is provided.
+ *
+ */
+class Request {
+public:
+ Request(Method method, std::string host, std::string port, const std::string &target) : host_data(std::move(host)), port_data(std::move(port))
+ {
+ header_data.append("GET ");
+ header_data.append(target);
+ header_data.append(" HTTP/1.1");
+ header_data.append("\r\n");
+ header_data.append("Host: ");
+ header_data.append(host_data);
+ header_data.append("\r\n");
+ header_data.append("\r\n");
+ };
+
+ void set_header_field(std::string const &field)
+ {
+ header_data.append(field);
+ }
+
+ void append_to_body(std::string const &data)
+ {
+ body_data.append(data);
+ };
+
+ const std::string &host() const
+ {
+ return host_data;
+ }
+
+ const std::string &service_port() const
+ {
+ return port_data;
+ }
+
+ const std::string &header() const
+ {
+ return header_data;
+ }
+
+ const std::string &body() const
+ {
+ return body_data;
+ }
+
+private:
+ std::string host_data;
+ std::string port_data;
+ std::string header_data;
+ std::string body_data;
+};
+
+/**
+ * @brief Simple HTTP response class
+ *
+ * The response is built from received data and only parsed to split header and body.
+ *
+ * A copy of the received data is kept.
+ *
+ */
+struct Response {
+ /**
+ * @brief Construct a response from a contiguous buffer.
+ *
+ * Simple http parsing.
+ *
+ */
+ template
+ explicit Response(DataIt data, size_t size)
+ {
+ raw_response = std::string(data, size);
+
+ auto header_last = raw_response.find("\r\n\r\n");
+ if (header_last != std::string::npos) {
+ header = raw_response.substr(0, header_last);
+ }
+ body = raw_response.substr(header_last + 3);
+ }
+ /**
+ * @brief Print response content.
+ */
+ void print()
+ {
+ ESP_LOGI(TAG, "Header :\n %s", header.c_str());
+ ESP_LOGI(TAG, "Body : \n %s", body.c_str());
+ }
+
+ std::string raw_response;
+ std::string header;
+ std::string body;
+};
+
+/** @brief HTTP Session
+ *
+ * Session class to handle HTTP protocol implementation.
+ *
+ */
+class Session : public std::enable_shared_from_this {
+public:
+ explicit Session(std::shared_ptr connection_in) : connection(std::move(connection_in))
+ {
+ }
+
+ template
+ void send_request(const Request &request, CompletionToken &&completion_handler)
+ {
+ auto self = shared_from_this();
+ send_data = { asio::buffer(request.header()), asio::buffer(request.body()) };
+ connection->write_async(send_data, [self, &completion_handler](std::error_code error, std::size_t bytes_transfered) {
+ if (error) {
+ ESP_LOGE(TAG, "Request write error: %s", error.message().c_str());
+ return;
+ }
+ ESP_LOGD(TAG, "Bytes Transfered: %d", bytes_transfered);
+ self->get_response(completion_handler);
+ });
+ }
+
+private:
+ template
+ void get_response(CompletionToken &&completion_handler)
+ {
+ auto self = shared_from_this();
+ connection->read_async(asio::buffer(receive_buffer), [self, &completion_handler](std::error_code error, std::size_t bytes_received) {
+ if (error and error.value() != asio::error::eof) {
+ return;
+ }
+ ESP_LOGD(TAG, "Bytes Received: %d", bytes_received);
+ if (bytes_received == 0) {
+ return;
+ }
+ Response response(std::begin(self->receive_buffer), bytes_received);
+
+ completion_handler(self, response);
+ });
+ }
+ /*
+ * For this example we assumed 2048 to be enough for the receive_buffer
+ */
+ std::array receive_buffer;
+ /*
+ * The hardcoded 2 below is related to the type we receive the data to send. We gather the parts from Request, header
+ * and body, to send avoiding the copy.
+ */
+ std::array send_data;
+ std::shared_ptr connection;
+};
+}// namespace Http
+
+extern "C" void app_main(void)
+{
+ // Basic initialization of ESP system
+ esp_init();
+
+ asio::io_context io_context;
+ Http::Request request(Http::Method::GET, "www.httpbin.org", "80", "/get");
+ Socks::async_connect(io_context, CONFIG_EXAMPLE_PROXY_ADDRESS, CONFIG_EXAMPLE_PROXY_SERVICE, request.host(), request.service_port(),
+ [&request](std::shared_ptr connection) {
+ // Now we create a HTTP::Session and inject the necessary connection.
+ std::make_shared(connection)->send_request(request, [](std::shared_ptr session, Http::Response response) {
+ response.print();
+ });
+ });
+ // io_context.run will block until all the tasks on the context are done.
+ io_context.run();
+ ESP_LOGI(TAG, "Context run done");
+
+ ESP_ERROR_CHECK(example_disconnect());
+}
diff --git a/components/asio/examples/socks4/main/socks4.hpp b/components/asio/examples/socks4/main/socks4.hpp
new file mode 100644
index 000000000..a039c07ca
--- /dev/null
+++ b/components/asio/examples/socks4/main/socks4.hpp
@@ -0,0 +1,143 @@
+//
+// socks4.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef SOCKS4_HPP
+#define SOCKS4_HPP
+
+#include
+#include
+#include
+#include
+
+namespace socks4 {
+
+const unsigned char version = 0x04;
+
+class request
+{
+public:
+ enum command_type
+ {
+ connect = 0x01,
+ bind = 0x02
+ };
+
+ request(command_type cmd, const asio::ip::tcp::endpoint& endpoint,
+ const std::string& user_id)
+ : version_(version),
+ command_(cmd),
+ user_id_(user_id),
+ null_byte_(0)
+ {
+ // Only IPv4 is supported by the SOCKS 4 protocol.
+ if (endpoint.protocol() != asio::ip::tcp::v4())
+ {
+ throw asio::system_error(
+ asio::error::address_family_not_supported);
+ }
+
+ // Convert port number to network byte order.
+ unsigned short port = endpoint.port();
+ port_high_byte_ = (port >> 8) & 0xff;
+ port_low_byte_ = port & 0xff;
+
+ // Save IP address in network byte order.
+ address_ = endpoint.address().to_v4().to_bytes();
+ }
+
+ std::array buffers() const
+ {
+ return
+ {
+ {
+ asio::buffer(&version_, 1),
+ asio::buffer(&command_, 1),
+ asio::buffer(&port_high_byte_, 1),
+ asio::buffer(&port_low_byte_, 1),
+ asio::buffer(address_),
+ asio::buffer(user_id_),
+ asio::buffer(&null_byte_, 1)
+ }
+ };
+ }
+
+private:
+ unsigned char version_;
+ unsigned char command_;
+ unsigned char port_high_byte_;
+ unsigned char port_low_byte_;
+ asio::ip::address_v4::bytes_type address_;
+ std::string user_id_;
+ unsigned char null_byte_;
+};
+
+class reply
+{
+public:
+ enum status_type
+ {
+ request_granted = 0x5a,
+ request_failed = 0x5b,
+ request_failed_no_identd = 0x5c,
+ request_failed_bad_user_id = 0x5d
+ };
+
+ reply()
+ : null_byte_(0),
+ status_()
+ {
+ }
+
+ std::array buffers()
+ {
+ return
+ {
+ {
+ asio::buffer(&null_byte_, 1),
+ asio::buffer(&status_, 1),
+ asio::buffer(&port_high_byte_, 1),
+ asio::buffer(&port_low_byte_, 1),
+ asio::buffer(address_)
+ }
+ };
+ }
+
+ bool success() const
+ {
+ return null_byte_ == 0 && status_ == request_granted;
+ }
+
+ unsigned char status() const
+ {
+ return status_;
+ }
+
+ asio::ip::tcp::endpoint endpoint() const
+ {
+ unsigned short port = port_high_byte_;
+ port = (port << 8) & 0xff00;
+ port = port | port_low_byte_;
+
+ asio::ip::address_v4 address(address_);
+
+ return asio::ip::tcp::endpoint(address, port);
+ }
+
+private:
+ unsigned char null_byte_;
+ unsigned char status_;
+ unsigned char port_high_byte_;
+ unsigned char port_low_byte_;
+ asio::ip::address_v4::bytes_type address_;
+};
+
+} // namespace socks4
+
+#endif // SOCKS4_HPP
diff --git a/components/asio/examples/socks4/sdkconfig.defaults b/components/asio/examples/socks4/sdkconfig.defaults
new file mode 100644
index 000000000..a6320e2c3
--- /dev/null
+++ b/components/asio/examples/socks4/sdkconfig.defaults
@@ -0,0 +1,3 @@
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
+CONFIG_COMPILER_CXX_RTTI=y
+CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
diff --git a/components/asio/examples/ssl_client_server/CMakeLists.txt b/components/asio/examples/ssl_client_server/CMakeLists.txt
new file mode 100644
index 000000000..73ee939a7
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/CMakeLists.txt
@@ -0,0 +1,11 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+set(EXCLUDE_COMPONENTS openssl)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(asio_ssl_client_server)
diff --git a/components/asio/examples/ssl_client_server/README.md b/components/asio/examples/ssl_client_server/README.md
new file mode 100644
index 000000000..fbf2cc15e
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/README.md
@@ -0,0 +1,88 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
+| ----------------- | ----- | -------- | -------- | -------- |
+
+# Asio SSL client/server example
+
+Simple Asio client and server with SSL/TLS transport
+
+## How to Use Example
+
+### Hardware Required
+
+This example can be executed on any ESP platform board. No external connection is required, it is recommended though
+to connect to internet or a local network via WiFi or Ethernet to easily exercise features of this example.
+
+### Configure the project
+
+* Open the project configuration menu (`idf.py menuconfig`)
+* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
+* Enable the ASIO client and set server's host name to examine client's functionality.
+The ASIO client connects to the configured server and sends default payload string "GET / HTTP/1.1"
+* Enable the ASIO server to examine server's functionality. The ASIO server listens to connection and echos back what was received.
+
+### Build and Flash
+
+Build the project and flash it to the board, then run monitor tool to view serial output:
+
+```
+idf.py -p PORT flash monitor
+```
+
+(To exit the serial monitor, type ``Ctrl-]``.)
+
+See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
+
+## Example Output
+
+### Client connecting to public server
+
+The below output illustrates the client connecting to a public https server.
+
+```
+I (1267) example_connect: Waiting for IP(s)
+I (2587) wifi:new:<11,0>, old:<1,0>, ap:<255,255>, sta:<11,0>, prof:1
+I (3367) wifi:state: init -> auth (b0)
+I (3377) wifi:state: auth -> assoc (0)
+I (3387) wifi:state: assoc -> run (10)
+I (3397) wifi:security type: 3, phy: bgn, rssi: -49
+I (3397) wifi:pm start, type: 1
+I (3457) wifi:AP's beacon interval = 102400 us, DTIM period = 1
+I (4747) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:260a:xxxx:xxxx:xxxx, type: ESP_IP6_ADDR_IS_LINK_LOCAL
+I (5247) esp_netif_handlers: example_connect: sta ip: 192.168.32.69, mask: 255.255.252.0, gw: 192.168.32.3
+I (5247) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.32.69
+I (5257) example_connect: Connected to example_connect: sta
+I (5257) example_connect: - IPv4 address: 192.168.32.69
+I (5267) example_connect: - IPv6 address: fe80:0000:0000:0000:260a:xxxx:xxxx:xxxx, type: ESP_IP6_ADDR_IS_LINK_LOCAL
+W (5277) esp32_asio_pthread: pthread_condattr_setclock: not yet supported!
+W (5297) esp32_asio_pthread: pthread_condattr_setclock: not yet supported!
+Reply: HTTP/1.1 200 OK
+D
+```
+### Both server and client enabled
+
+The below output demonstrates the client connecting to the ASIO server via loopback interface, so no WiFi, nor Ethernet connection
+was established.
+```
+I (0) cpu_start: App cpu up.
+I (495) heap_init: Initializing. RAM available for dynamic allocation:
+I (502) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
+I (508) heap_init: At 3FFB5400 len 0002AC00 (171 KiB): DRAM
+I (515) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
+I (521) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
+I (527) heap_init: At 4008BB80 len 00014480 (81 KiB): IRAM
+I (534) cpu_start: Pro cpu start user code
+I (556) spi_flash: detected chip: gd
+I (556) spi_flash: flash io: dio
+W (556) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
+I (566) cpu_start: Starting scheduler on PRO CPU.
+I (0) cpu_start: Starting scheduler on APP CPU.
+I (600) example_connect: Waiting for IP(s)
+W (600) esp32_asio_pthread: pthread_condattr_setclock: not yet supported!
+W (1610) esp32_asio_pthread: pthread_condattr_setclock: not yet supported!
+W (1610) esp32_asio_pthread: pthread_condattr_setclock: not yet supported!
+Server received: GET / HTTP/1.1
+
+
+Reply: GET / HTTP/1.1
+```
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/components/asio/examples/ssl_client_server/example_test.py b/components/asio/examples/ssl_client_server/example_test.py
new file mode 100644
index 000000000..e819380e3
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/example_test.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+
+import ttfw_idf
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_GENERIC', target=['esp32', 'esp32c3'])
+def test_examples_asio_ssl(env, extra_data):
+
+ dut = env.get_dut('asio_ssl_client_server', 'examples/protocols/asio/ssl_client_server')
+ dut.start_app()
+
+ dut.expect('Reply: GET / HTTP/1.1')
+
+
+if __name__ == '__main__':
+ test_examples_asio_ssl()
diff --git a/components/asio/examples/ssl_client_server/main/CMakeLists.txt b/components/asio/examples/ssl_client_server/main/CMakeLists.txt
new file mode 100644
index 000000000..f962b470c
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/CMakeLists.txt
@@ -0,0 +1,3 @@
+idf_component_register(SRCS "asio_ssl_main.cpp"
+ INCLUDE_DIRS "."
+ EMBED_TXTFILES ca.crt server.key srv.crt)
diff --git a/components/asio/examples/ssl_client_server/main/Kconfig.projbuild b/components/asio/examples/ssl_client_server/main/Kconfig.projbuild
new file mode 100644
index 000000000..1d03a2af4
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/Kconfig.projbuild
@@ -0,0 +1,36 @@
+menu "Example Configuration"
+
+ config EXAMPLE_CLIENT
+ bool "Enable TLS client"
+ default y
+ help
+ Choose this option to use ASIO TLS/SSL client functionality
+
+ config EXAMPLE_PORT
+ string "ASIO port number"
+ default "443"
+ help
+ Port number used by ASIO example.
+
+ config EXAMPLE_SERVER
+ bool "Enable TLS server"
+ default n
+ help
+ Choose this option to use ASIO TLS/SSL server functionality
+
+ config EXAMPLE_SERVER_NAME
+ string "ASIO server name or IP"
+ default "www.google.com"
+ depends on EXAMPLE_CLIENT
+ help
+ Asio example server ip for the ASIO client to connect to.
+
+ config EXAMPLE_CLIENT_VERIFY_PEER
+ bool "Client to verify peer"
+ default n
+ depends on EXAMPLE_CLIENT
+ help
+ This option sets client's mode to verify peer, default is
+ verify-none
+
+endmenu
diff --git a/components/asio/examples/ssl_client_server/main/asio_ssl_main.cpp b/components/asio/examples/ssl_client_server/main/asio_ssl_main.cpp
new file mode 100644
index 000000000..8356dc716
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/asio_ssl_main.cpp
@@ -0,0 +1,272 @@
+//
+// Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include
+#include "protocol_examples_common.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+#include
+#include
+#include
+#include
+#include "asio.hpp"
+#include "asio/ssl.hpp"
+#include "asio/buffer.hpp"
+#include "esp_pthread.h"
+
+extern const unsigned char server_pem_start[] asm("_binary_srv_crt_start");
+extern const unsigned char server_pem_end[] asm("_binary_srv_crt_end");
+
+extern const unsigned char cacert_pem_start[] asm("_binary_ca_crt_start");
+extern const unsigned char cacert_pem_end[] asm("_binary_ca_crt_end");
+
+extern const unsigned char prvtkey_pem_start[] asm("_binary_server_key_start");
+extern const unsigned char prvtkey_pem_end[] asm("_binary_server_key_end");
+
+static const asio::const_buffer cert_chain(cacert_pem_start, cacert_pem_end - cacert_pem_start);
+static const asio::const_buffer privkey(prvtkey_pem_start, prvtkey_pem_end - prvtkey_pem_start);
+static const asio::const_buffer server_cert(server_pem_start, server_pem_end - server_pem_start);
+
+using asio::ip::tcp;
+
+static const std::size_t max_length = 1024;
+
+class Client {
+public:
+ Client(asio::io_context &io_context,
+ asio::ssl::context &context,
+ const tcp::resolver::results_type &endpoints)
+ : socket_(io_context, context)
+ {
+
+#if CONFIG_EXAMPLE_CLIENT_VERIFY_PEER
+ socket_.set_verify_mode(asio::ssl::verify_peer);
+#else
+ socket_.set_verify_mode(asio::ssl::verify_none);
+#endif // CONFIG_EXAMPLE_CLIENT_VERIFY_PEER
+
+ connect(endpoints);
+ }
+
+private:
+ void connect(const tcp::resolver::results_type &endpoints)
+ {
+ asio::async_connect(socket_.lowest_layer(), endpoints,
+ [this](const std::error_code & error,
+ const tcp::endpoint & /*endpoint*/) {
+ if (!error) {
+ handshake();
+ } else {
+ std::cout << "Connect failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void handshake()
+ {
+ socket_.async_handshake(asio::ssl::stream_base::client,
+ [this](const std::error_code & error) {
+ if (!error) {
+ send_request();
+ } else {
+ std::cout << "Handshake failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void send_request()
+ {
+ size_t request_length = std::strlen(request_);
+
+ asio::async_write(socket_,
+ asio::buffer(request_, request_length),
+ [this](const std::error_code & error, std::size_t length) {
+ if (!error) {
+ receive_response(length);
+ } else {
+ std::cout << "Write failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ void receive_response(std::size_t length)
+ {
+ asio::async_read(socket_,
+ asio::buffer(reply_, length),
+ [this](const std::error_code & error, std::size_t length) {
+ if (!error) {
+ std::cout << "Reply: ";
+ std::cout.write(reply_, length);
+ std::cout << "\n";
+ } else {
+ std::cout << "Read failed: " << error.message() << "\n";
+ }
+ });
+ }
+
+ asio::ssl::stream socket_;
+ char request_[max_length] = "GET / HTTP/1.1\r\n\r\n";
+ char reply_[max_length];
+};
+
+class Session : public std::enable_shared_from_this {
+public:
+ Session(tcp::socket socket, asio::ssl::context &context)
+ : socket_(std::move(socket), context)
+ {
+ }
+
+ void start()
+ {
+ do_handshake();
+ }
+
+private:
+ void do_handshake()
+ {
+ auto self(shared_from_this());
+ socket_.async_handshake(asio::ssl::stream_base::server,
+ [this, self](const std::error_code & error) {
+ if (!error) {
+ do_read();
+ }
+ });
+ }
+
+ void do_read()
+ {
+ auto self(shared_from_this());
+ socket_.async_read_some(asio::buffer(data_),
+ [this, self](const std::error_code & ec, std::size_t length) {
+ if (!ec) {
+ std::cout << "Server received: ";
+ std::cout.write(data_, length);
+ std::cout << std::endl;
+ do_write(length);
+ }
+ });
+ }
+
+ void do_write(std::size_t length)
+ {
+ auto self(shared_from_this());
+ asio::async_write(socket_, asio::buffer(data_, length),
+ [this, self](const std::error_code & ec,
+ std::size_t /*length*/) {
+ if (!ec) {
+ do_read();
+ }
+ });
+ }
+
+ asio::ssl::stream socket_;
+ char data_[max_length];
+};
+
+class Server {
+public:
+ Server(asio::io_context &io_context, unsigned short port)
+ : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
+ context_(asio::ssl::context::tls_server)
+ {
+ context_.set_options(
+ asio::ssl::context::default_workarounds
+ | asio::ssl::context::no_sslv2);
+ context_.use_certificate_chain(server_cert);
+ context_.use_private_key(privkey, asio::ssl::context::pem);
+
+ do_accept();
+ }
+
+private:
+ void do_accept()
+ {
+ acceptor_.async_accept(
+ [this](const std::error_code & error, tcp::socket socket) {
+ if (!error) {
+ std::make_shared(std::move(socket), context_)->start();
+ }
+
+ do_accept();
+ });
+ }
+
+ tcp::acceptor acceptor_;
+ asio::ssl::context context_;
+};
+
+void set_thread_config(const char *name, int stack, int prio)
+{
+ auto cfg = esp_pthread_get_default_config();
+ cfg.thread_name = name;
+ cfg.stack_size = stack;
+ cfg.prio = prio;
+ esp_pthread_set_cfg(&cfg);
+}
+
+void ssl_server_thread()
+{
+ asio::io_context io_context;
+
+ Server s(io_context, 443);
+
+ io_context.run();
+}
+
+void ssl_client_thread()
+{
+ asio::io_context io_context;
+
+ tcp::resolver resolver(io_context);
+ std::string server_ip = CONFIG_EXAMPLE_SERVER_NAME;
+ std::string server_port = CONFIG_EXAMPLE_PORT;
+ auto endpoints = resolver.resolve(server_ip, server_port);
+
+ asio::ssl::context ctx(asio::ssl::context::tls_client);
+#if CONFIG_EXAMPLE_CLIENT_VERIFY_PEER
+ ctx.add_certificate_authority(cert_chain);
+#endif // CONFIG_EXAMPLE_CLIENT_VERIFY_PEER
+
+ Client c(io_context, ctx, endpoints);
+
+ io_context.run();
+
+}
+
+
+extern "C" void app_main(void)
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ esp_netif_init();
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+ /* This helper function configures blocking UART I/O */
+ ESP_ERROR_CHECK(example_configure_stdin_stdout());
+ std::vector work_threads;
+
+#if CONFIG_EXAMPLE_SERVER
+ set_thread_config("Server", 16 * 1024, 5);
+ work_threads.emplace_back(std::thread(ssl_server_thread));
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+#endif // CONFIG_EXAMPLE_SERVER
+
+#if CONFIG_EXAMPLE_CLIENT
+ set_thread_config("Client", 16 * 1024, 5);
+ work_threads.emplace_back(ssl_client_thread);
+#endif // CONFIG_EXAMPLE_CLIENT
+
+ for (auto &t : work_threads) {
+ t.join();
+ }
+
+}
diff --git a/components/asio/examples/ssl_client_server/main/ca.crt b/components/asio/examples/ssl_client_server/main/ca.crt
new file mode 100644
index 000000000..894f2959b
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/ca.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkzCCAnugAwIBAgIUNI5wldYysh6rtCzYmda6H414aRswDQYJKoZIhvcNAQEL
+BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJRXNwcmVzc2lmMB4X
+DTIwMDEyMTA5MDk0NloXDTI1MDEyMDA5MDk0NlowWTELMAkGA1UEBhMCQVUxEzAR
+BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
+IEx0ZDESMBAGA1UEAwwJRXNwcmVzc2lmMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAyadSpRnIQBVbEAsbpkrKrOMlBOMIUmA8AfNyOYPLfv0Oa5lBiMAV
+3OQDu5tYyFYKwkCUqq65iAm50fPbSH71w1tkja6nZ1yAIM+TvpMlM/WiFGrhY+Tc
+kAcLcKUJyPxrv/glzoVslbqUgIhuhCSKA8uk1+ILcn3nWzPcbcowLx31+AHeZj8h
+bIAdj6vjqxMCFStp4IcA+ikmCk75LCN4vkkifdkebb/ZDNYCZZhpCBnCHyFAjPc4
+7C+FDVGT3/UUeeTy+Mtn+MqUAhB+W0sPDm1n2h59D4Z/MFm0hl6GQCAKeMJPzssU
+BBsRm6zoyPQ4VTqG0uwfNNbORyIfKONMUwIDAQABo1MwUTAdBgNVHQ4EFgQUGYLV
+EkgWzxjpltE6texha7zZVxowHwYDVR0jBBgwFoAUGYLVEkgWzxjpltE6texha7zZ
+VxowDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb2EF4Zg2XWNb
+eZHnzupCDd9jAhwPqkt7F1OXvxJa/RFUSB9+2izGvikGGhuKY4f0iLuqF+bhExD9
+sapDcdFO2Suh4J3onbwEvmKvsv56K3xhapYg8WwPofpkVirnkwFjpQXGzrYxPujg
+BPmSy3psQrhvOr/WH7SefJv2qr4ikaugfE+3enY4PL+C1dSQAuNo1QGgWsZIu0c8
+TZybNZ13vNVMA+tgj2CM8FR3Etaabwtu3TTcAnO7aoBTix/bLBTuZoczhN8/MhG3
+GylmDzFI8a6aKxQL3Fi4PsM82hRKWu3gfs39sR1Ci4V22v8uO5EWBPK0QZvDSc1a
+KwwxI4zA0w==
+-----END CERTIFICATE-----
diff --git a/components/asio/examples/ssl_client_server/main/server.key b/components/asio/examples/ssl_client_server/main/server.key
new file mode 100644
index 000000000..2a4d650ea
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAlUCywNhVv4RO2y9h/XGKZ1azzk3jzHpSBzIGO9LoiA8trC/p
+1ykGaUfYPJllYK4HMhC4fUyE3J7tVL2Eskzl26LNPLbEoaBWZM9NhV3iA1/1EtOu
+p6umLx+y3sDfvK35YAOUbjdAlBfhnJ4r8h7oTsxl3J5jZ18zgjJnJi2NEFq/yTpO
+MiwHLWPjy25fDFixfV9UzSvbgt1JaGPmC7c4QkhHzjyp0+ikuvRIw0p9BBNeqBV2
+da3qBMB5FtodUJTAz6o6OKWbTalLjQi6C1H6z9TnY7IrJBUOy/FWkQH/sEsLdscD
+hHa1Dz2oT203QjhzyOSfnNF95D/1MdNcMt6l0wIDAQABAoIBAC1JJTOoMFRc48RT
+myrYQYNbZlEphv3q+2qdfhC2zMFDwbrmCtCy7PQSzYSNkpoEE8DYG/JAvmtmeWJl
+4pZrCK9ctWM/nWfhC3WpBL97nfEiM20T94F+bn0L5Cz8XqaULv839th+QUTt/hGU
+WIctY5VNJXcMQ+MAmtNdUbjex1d3iuxiKHUo4nDoZ8digKFNdtdP5B5nlMq5chCL
+mxNRcsGsx2dDAxbGUapdTVPWHPJKpLOBoSkluDsfd2KZADFU2R1SJpAX9+RYh3HM
+5FTUdHTUaISxbKkgeDKlEM0lqk2TtGUwCyEj098ewi7Wzsu9w60IplPPUJx5FRG6
+jp3wzLkCgYEAxKp5T20rf/7ysX7x053I7VCjDXUxAaWOEj1uS3AhOkl0NaZg7Di+
+y53fWNkcHdkt2n2LqMt/43UgMYq3TVVcq2eunPNF11e1bJw8CjDafwDs4omwwyVn
+lYhPuB4dK2OAib+vU5Zqpp0kZMoxk2MZVgon8z+s8DW/zmB6aFqAWeUCgYEAwkhC
+OgmXKMdjOCVy5t2f5UbY8Y9rV3w8eUATuJ47MMwLr4pGYnKoEn9JB4ltWrHv/u5S
+fOv3tIrrCEvnCoCbOILwCsY5LqTNXgqova8FB6RpMUQCzhDd8LHuvdHv0WMnMzX1
+3PKuqwh8JS55m4WqZRhzr5BFKG4fHPVs4IcaJVcCgYAzzCaJSdqUKqTnJOUydDNQ
+ddWMHNqccWs62J0tF0pZHLGT089hSAzQejMyJnSmU+Ykzr4y5e44DUg+ZCelIZ93
+saYmxlgVwI8THQ8fLADQRIEfpV4996MRmkZM2vmZzOo03Zyi6lIKsga82Rg3lnk8
+1Q3ynknBNpbfF0AGLhfyFQKBgBYlxJ73HutAJ5hr9HhLBYJOnEaVUehMOlycKGNg
+bmD2sdJWEgYBChXpurqIORYguLo4EuE4ySkkuPxeIr14wbkkfBbOWBBwKxUwY+IT
+xKAFZxR9q1AwbgyVTCEJgKw/AGX/HcMNS0omEnjunmBTUYRq0C1QZgHg490aQUor
+PJjLAoGAevzdTpFlVeuKeYh1oDubGO1LinyXpBv7fPFjl+zu4AVbjojcU6yC4OO6
+QvqopE6SyAECKy8kAOFcESPsGc9Lta2XUvI203z7pIVlNVEcJ0+90mQh3Mn1U46l
+sZ49PdRvNwNb5wvkh1UqNsMlGFbRlzMbIk45ou4311kCobowZek=
+-----END RSA PRIVATE KEY-----
diff --git a/components/asio/examples/ssl_client_server/main/srv.crt b/components/asio/examples/ssl_client_server/main/srv.crt
new file mode 100644
index 000000000..29bfa1664
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/main/srv.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9DCCAdwCFA1lSIcHwYKdB2UqOrZxZnVgPObTMA0GCSqGSIb3DQEBCwUAMFkx
+CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
+cm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCUVzcHJlc3NpZjAeFw0yMDA2
+MTIwNjA0MTNaFw0yMjA2MDIwNjA0MTNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJVAssDYVb+ETtsvYf1ximdW
+s85N48x6UgcyBjvS6IgPLawv6dcpBmlH2DyZZWCuBzIQuH1MhNye7VS9hLJM5dui
+zTy2xKGgVmTPTYVd4gNf9RLTrqerpi8fst7A37yt+WADlG43QJQX4ZyeK/Ie6E7M
+ZdyeY2dfM4IyZyYtjRBav8k6TjIsBy1j48tuXwxYsX1fVM0r24LdSWhj5gu3OEJI
+R848qdPopLr0SMNKfQQTXqgVdnWt6gTAeRbaHVCUwM+qOjilm02pS40IugtR+s/U
+52OyKyQVDsvxVpEB/7BLC3bHA4R2tQ89qE9tN0I4c8jkn5zRfeQ/9THTXDLepdMC
+AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnMYGW+idt37bEE4WPgrRorKWuplR+zHD
+wJFz53DQzyIZJHmJ2hR5U0jNcHy/nMq7tbdz9LZPrVF4lZJ3TJhnmkOKjMFPCQE8
+YcmsP3il6eXgtGqg53InOi/uJqEQ9TfM54cbpp6xKbnmpwk4uprISBRQt7u2ZLk2
+40ED6zgjFPDTYmSjSpb2AN6KUB6PflgVs+4p9ViHNq4U3AlYV/BM0+3G4aMX2wNl
+ZIpQfOyuaYD5MU50mY+O+gDiiypkpYf6a6S4YJ1sMbavDsP7bW5UMnP0jKYR549q
+5hF1fdkXq52DfJ9ya2kl3mANFkKssQV+1KCBMxGoeqfakmJfa03xXA==
+-----END CERTIFICATE-----
diff --git a/components/asio/examples/ssl_client_server/partitions.csv b/components/asio/examples/ssl_client_server/partitions.csv
new file mode 100644
index 000000000..f3aa8e2b4
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/partitions.csv
@@ -0,0 +1,5 @@
+# Name, Type, SubType, Offset, Size, Flags
+# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
+nvs, data, nvs, 0x9000, 0x6000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 1400000,
diff --git a/components/asio/examples/ssl_client_server/sdkconfig.ci b/components/asio/examples/ssl_client_server/sdkconfig.ci
new file mode 100644
index 000000000..f1c43e8ac
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/sdkconfig.ci
@@ -0,0 +1,6 @@
+CONFIG_EXAMPLE_CLIENT=y
+CONFIG_EXAMPLE_SERVER=y
+CONFIG_EXAMPLE_SERVER_NAME="localhost"
+CONFIG_EXAMPLE_CONNECT_WIFI=n
+CONFIG_EXAMPLE_CONNECT_ETHERNET=n
+CONFIG_EXAMPLE_CLIENT_VERIFY_PEER=y
diff --git a/components/asio/examples/ssl_client_server/sdkconfig.defaults b/components/asio/examples/ssl_client_server/sdkconfig.defaults
new file mode 100644
index 000000000..30bba61d4
--- /dev/null
+++ b/components/asio/examples/ssl_client_server/sdkconfig.defaults
@@ -0,0 +1,10 @@
+CONFIG_ASIO_SSL_SUPPORT=y
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
+
+#
+# Partition Table
+#
+# Leave some room for larger apps without needing to reduce other features
+CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
diff --git a/components/asio/examples/tcp_echo_server/CMakeLists.txt b/components/asio/examples/tcp_echo_server/CMakeLists.txt
new file mode 100644
index 000000000..29e290677
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(asio_tcp_echo_server)
diff --git a/components/asio/examples/tcp_echo_server/README.md b/components/asio/examples/tcp_echo_server/README.md
new file mode 100644
index 000000000..2c3893550
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/README.md
@@ -0,0 +1,22 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
+| ----------------- | ----- | -------- | -------- | -------- |
+
+# Asio TCP echo server example
+
+Simple Asio TCP echo server using WiFi STA or Ethernet.
+
+## Example workflow
+
+- Wi-Fi or Ethernet connection is established, and IP address is obtained.
+- Asio TCP server is started on port number defined through the project configuration.
+- Server receives and echoes back messages transmitted from client.
+
+## Running the example
+
+- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
+- Set server port number in menuconfig, "Example configuration".
+- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
+- Wait for the board to connect to WiFi or Ethernet (note the IP address).
+- You can now send a TCP message and check it is repeated, for example using netcat `nc IP PORT`.
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/components/asio/examples/tcp_echo_server/asio_tcp_server_test.py b/components/asio/examples/tcp_echo_server/asio_tcp_server_test.py
new file mode 100644
index 000000000..0293350ab
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/asio_tcp_server_test.py
@@ -0,0 +1,47 @@
+import os
+import re
+import socket
+
+import ttfw_idf
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols')
+def test_examples_protocol_asio_tcp_server(env, extra_data):
+ """
+ steps: |
+ 1. join AP
+ 2. Start server
+ 3. Test connects to server and sends a test message
+ 4. Test evaluates received test message from server
+ 5. Test evaluates received test message on server stdout
+ """
+ test_msg = b'echo message from client to server'
+ dut1 = env.get_dut('tcp_echo_server', 'examples/protocols/asio/tcp_echo_server', dut_class=ttfw_idf.ESP32DUT)
+ # check and log bin size
+ binary_file = os.path.join(dut1.app.binary_path, 'asio_tcp_echo_server.bin')
+ bin_size = os.path.getsize(binary_file)
+ ttfw_idf.log_performance('asio_tcp_echo_server_bin_size', '{}KB'.format(bin_size // 1024))
+ # 1. start test
+ dut1.start_app()
+ # 2. get the server IP address
+ data = dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
+ # 3. create tcp client and connect to server
+ dut1.expect('ASIO engine is up and running', timeout=1)
+ cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ cli.settimeout(30)
+ cli.connect((data[0], 2222))
+ cli.send(test_msg)
+ data = cli.recv(1024)
+ # 4. check the message received back from the server
+ if (data == test_msg):
+ print('PASS: Received correct message')
+ pass
+ else:
+ print('Failure!')
+ raise ValueError('Wrong data received from asi tcp server: {} (expected:{})'.format(data, test_msg))
+ # 5. check the client message appears also on server terminal
+ dut1.expect(test_msg.decode())
+
+
+if __name__ == '__main__':
+ test_examples_protocol_asio_tcp_server()
diff --git a/components/asio/examples/tcp_echo_server/main/CMakeLists.txt b/components/asio/examples/tcp_echo_server/main/CMakeLists.txt
new file mode 100644
index 000000000..fcd492bca
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "echo_server.cpp"
+ INCLUDE_DIRS ".")
diff --git a/components/asio/examples/tcp_echo_server/main/Kconfig.projbuild b/components/asio/examples/tcp_echo_server/main/Kconfig.projbuild
new file mode 100644
index 000000000..795326db9
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/main/Kconfig.projbuild
@@ -0,0 +1,9 @@
+menu "Example Configuration"
+
+ config EXAMPLE_PORT
+ string "Asio example port number"
+ default "2222"
+ help
+ Port number used by Asio example.
+
+endmenu
diff --git a/components/asio/examples/tcp_echo_server/main/echo_server.cpp b/components/asio/examples/tcp_echo_server/main/echo_server.cpp
new file mode 100644
index 000000000..5c520aeac
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/main/echo_server.cpp
@@ -0,0 +1,108 @@
+#include "asio.hpp"
+#include
+#include
+#include "protocol_examples_common.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+
+using asio::ip::tcp;
+
+class session
+ : public std::enable_shared_from_this
+{
+public:
+ session(tcp::socket socket)
+ : socket_(std::move(socket))
+ {
+ }
+
+ void start()
+ {
+ do_read();
+ }
+
+private:
+ void do_read()
+ {
+ auto self(shared_from_this());
+ socket_.async_read_some(asio::buffer(data_, max_length),
+ [this, self](std::error_code ec, std::size_t length)
+ {
+ if (!ec)
+ {
+ data_[length] = 0;
+ std::cout << data_ << std::endl;
+ do_write(length);
+ }
+ });
+ }
+
+ void do_write(std::size_t length)
+ {
+ auto self(shared_from_this());
+ asio::async_write(socket_, asio::buffer(data_, length),
+ [this, self](std::error_code ec, std::size_t length)
+ {
+ if (!ec)
+ {
+ do_read();
+ }
+ });
+ }
+
+ tcp::socket socket_;
+ enum { max_length = 1024 };
+ char data_[max_length];
+};
+
+class server
+{
+public:
+ server(asio::io_context& io_context, short port)
+ : acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
+ {
+ do_accept();
+ }
+
+private:
+ void do_accept()
+ {
+ acceptor_.async_accept(
+ [this](std::error_code ec, tcp::socket socket)
+ {
+ if (!ec)
+ {
+ std::make_shared(std::move(socket))->start();
+ }
+
+ do_accept();
+ });
+ }
+
+ tcp::acceptor acceptor_;
+};
+
+
+extern "C" void app_main(void)
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ esp_netif_init();
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+ /* This helper function configures blocking UART I/O */
+ ESP_ERROR_CHECK(example_configure_stdin_stdout());
+
+ asio::io_context io_context;
+
+ server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT));
+
+ std::cout << "ASIO engine is up and running" << std::endl;
+
+ io_context.run();
+}
diff --git a/components/asio/examples/tcp_echo_server/sdkconfig.defaults b/components/asio/examples/tcp_echo_server/sdkconfig.defaults
new file mode 100644
index 000000000..b02a3a3ef
--- /dev/null
+++ b/components/asio/examples/tcp_echo_server/sdkconfig.defaults
@@ -0,0 +1,7 @@
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+
+#
+# Partition Table
+#
+# Leave some room for larger apps without needing to reduce other features
+CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
diff --git a/components/asio/examples/udp_echo_server/CMakeLists.txt b/components/asio/examples/udp_echo_server/CMakeLists.txt
new file mode 100644
index 000000000..2e0c28024
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/CMakeLists.txt
@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.16)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS ../../../../common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(asio_udp_echo_server)
diff --git a/components/asio/examples/udp_echo_server/README.md b/components/asio/examples/udp_echo_server/README.md
new file mode 100644
index 000000000..cd384fc39
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/README.md
@@ -0,0 +1,22 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
+| ----------------- | ----- | -------- | -------- | -------- |
+
+# Asio UDP echo server example
+
+Simple Asio UDP echo server using WiFi STA or Ethernet.
+
+## Example workflow
+
+- Wi-Fi or Ethernet connection is established, and IP address is obtained.
+- Asio UDP server is started on port number defined through the project configuration
+- Server receives and echoes back messages transmitted from client
+
+## Running the example
+
+- Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
+- Set server port number in menuconfig, "Example configuration".
+- Run `idf.py -p PORT flash monitor` to build and upload the example to your board and connect to it's serial terminal.
+- Wait for the board to connect to WiFi or Ethernet (note the IP address).
+- You can now send a UDP message and check it is repeated, for example using netcat `nc -u IP PORT`.
+
+See the README.md file in the upper level 'examples' directory for more information about examples.
diff --git a/components/asio/examples/udp_echo_server/asio_udp_server_test.py b/components/asio/examples/udp_echo_server/asio_udp_server_test.py
new file mode 100644
index 000000000..b2c2a5f75
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/asio_udp_server_test.py
@@ -0,0 +1,47 @@
+import os
+import re
+import socket
+
+import ttfw_idf
+
+
+@ttfw_idf.idf_example_test(env_tag='Example_WIFI_Protocols')
+def test_examples_protocol_asio_udp_server(env, extra_data):
+ """
+ steps: |
+ 1. join AP
+ 2. Start server
+ 3. Test connects to server and sends a test message
+ 4. Test evaluates received test message from server
+ 5. Test evaluates received test message on server stdout
+ """
+ test_msg = b'echo message from client to server'
+ dut1 = env.get_dut('udp_echo_server', 'examples/protocols/asio/udp_echo_server', dut_class=ttfw_idf.ESP32DUT)
+ # check and log bin size
+ binary_file = os.path.join(dut1.app.binary_path, 'asio_udp_echo_server.bin')
+ bin_size = os.path.getsize(binary_file)
+ ttfw_idf.log_performance('asio_udp_echo_server_bin_size', '{}KB'.format(bin_size // 1024))
+ # 1. start test
+ dut1.start_app()
+ # 2. get the server IP address
+ data = dut1.expect(re.compile(r' IPv4 address: ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)'), timeout=30)
+ # 3. create tcp client and connect to server
+ dut1.expect('ASIO engine is up and running', timeout=1)
+ cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ cli.settimeout(30)
+ cli.connect((data[0], 2222))
+ cli.send(test_msg)
+ data = cli.recv(1024)
+ # 4. check the message received back from the server
+ if (data == test_msg):
+ print('PASS: Received correct message')
+ pass
+ else:
+ print('Failure!')
+ raise ValueError('Wrong data received from asio udp server: {} (expected:{})'.format(data, test_msg))
+ # 5. check the client message appears also on server terminal
+ dut1.expect(test_msg.decode())
+
+
+if __name__ == '__main__':
+ test_examples_protocol_asio_udp_server()
diff --git a/components/asio/examples/udp_echo_server/main/CMakeLists.txt b/components/asio/examples/udp_echo_server/main/CMakeLists.txt
new file mode 100644
index 000000000..c692c0905
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/main/CMakeLists.txt
@@ -0,0 +1,2 @@
+idf_component_register(SRCS "udp_echo_server.cpp"
+ INCLUDE_DIRS ".")
diff --git a/components/asio/examples/udp_echo_server/main/Kconfig.projbuild b/components/asio/examples/udp_echo_server/main/Kconfig.projbuild
new file mode 100644
index 000000000..795326db9
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/main/Kconfig.projbuild
@@ -0,0 +1,9 @@
+menu "Example Configuration"
+
+ config EXAMPLE_PORT
+ string "Asio example port number"
+ default "2222"
+ help
+ Port number used by Asio example.
+
+endmenu
diff --git a/components/asio/examples/udp_echo_server/main/udp_echo_server.cpp b/components/asio/examples/udp_echo_server/main/udp_echo_server.cpp
new file mode 100644
index 000000000..5e21d5100
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/main/udp_echo_server.cpp
@@ -0,0 +1,90 @@
+//
+// async_udp_echo_server.cpp
+// ~~~~~~~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include
+#include
+
+#include "asio.hpp"
+
+#include "protocol_examples_common.h"
+#include "esp_event.h"
+#include "nvs_flash.h"
+
+
+using asio::ip::udp;
+
+class server
+{
+public:
+ server(asio::io_context& io_context, short port)
+ : socket_(io_context, udp::endpoint(udp::v4(), port))
+ {
+ do_receive();
+ }
+
+ void do_receive()
+ {
+ socket_.async_receive_from(
+ asio::buffer(data_, max_length), sender_endpoint_,
+ [this](std::error_code ec, std::size_t bytes_recvd)
+ {
+ if (!ec && bytes_recvd > 0)
+ {
+ data_[bytes_recvd] = 0;
+ std::cout << data_ << std::endl;
+ do_send(bytes_recvd);
+ }
+ else
+ {
+ do_receive();
+ }
+ });
+ }
+
+ void do_send(std::size_t length)
+ {
+ socket_.async_send_to(
+ asio::buffer(data_, length), sender_endpoint_,
+ [this](std::error_code /*ec*/, std::size_t bytes /*bytes_sent*/)
+ {
+ do_receive();
+ });
+ }
+
+private:
+ udp::socket socket_;
+ udp::endpoint sender_endpoint_;
+ enum { max_length = 1024 };
+ char data_[max_length];
+};
+
+extern "C" void app_main(void)
+{
+ ESP_ERROR_CHECK(nvs_flash_init());
+ esp_netif_init();
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
+ * Read "Establishing Wi-Fi or Ethernet Connection" section in
+ * examples/protocols/README.md for more information about this function.
+ */
+ ESP_ERROR_CHECK(example_connect());
+
+ /* This helper function configures blocking UART I/O */
+ ESP_ERROR_CHECK(example_configure_stdin_stdout());
+
+ asio::io_context io_context;
+
+ server s(io_context, std::atoi(CONFIG_EXAMPLE_PORT));
+
+ std::cout << "ASIO engine is up and running" << std::endl;
+
+ io_context.run();
+}
diff --git a/components/asio/examples/udp_echo_server/sdkconfig.defaults b/components/asio/examples/udp_echo_server/sdkconfig.defaults
new file mode 100644
index 000000000..b02a3a3ef
--- /dev/null
+++ b/components/asio/examples/udp_echo_server/sdkconfig.defaults
@@ -0,0 +1,7 @@
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+
+#
+# Partition Table
+#
+# Leave some room for larger apps without needing to reduce other features
+CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
diff --git a/components/asio/idf_component.yml b/components/asio/idf_component.yml
new file mode 100644
index 000000000..b2e11a570
--- /dev/null
+++ b/components/asio/idf_component.yml
@@ -0,0 +1,5 @@
+version: "1.0.1"
+description: ASIO
+dependencies:
+ idf:
+ version: ">=5.0"
diff --git a/components/asio/port/include/esp_asio_config.h b/components/asio/port/include/esp_asio_config.h
new file mode 100644
index 000000000..cf42183a3
--- /dev/null
+++ b/components/asio/port/include/esp_asio_config.h
@@ -0,0 +1,45 @@
+/*
+ * SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ESP_ASIO_CONFIG_H_
+#define _ESP_ASIO_CONFIG_H_
+
+//
+// Enabling exceptions only when they are enabled in menuconfig
+//
+# include
+# ifndef CONFIG_COMPILER_CXX_EXCEPTIONS
+# define ASIO_NO_EXCEPTIONS
+# endif // CONFIG_COMPILER_CXX_EXCEPTIONS
+
+# ifndef CONFIG_COMPILER_RTTI
+# define ASIO_NO_TYPEID
+# endif // CONFIG_COMPILER_RTTI
+
+//
+// Use system sockets
+//
+# include "sys/socket.h"
+
+//
+// Specific ASIO feature flags
+//
+# define ASIO_DISABLE_SERIAL_PORT
+# define ASIO_SEPARATE_COMPILATION
+# define ASIO_STANDALONE
+# define ASIO_HAS_PTHREADS
+# define ASIO_DISABLE_CONCEPTS
+
+# ifdef CONFIG_ASIO_USE_ESP_OPENSSL
+# define ASIO_USE_ESP_OPENSSL
+# define OPENSSL_NO_ENGINE
+# define ASIO_SSL_DETAIL_OPENSSL_TYPES_HPP
+# include "openssl_stub.hpp"
+
+# elif CONFIG_ASIO_USE_ESP_WOLFSSL
+# define ASIO_USE_WOLFSSL
+# endif // CONFIG_ASIO_USE_ESP_OPENSSL
+
+#endif // _ESP_ASIO_CONFIG_H_
diff --git a/components/asio/port/include/esp_exception.h b/components/asio/port/include/esp_exception.h
new file mode 100644
index 000000000..15cada7a7
--- /dev/null
+++ b/components/asio/port/include/esp_exception.h
@@ -0,0 +1,30 @@
+/*
+ * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef _ESP_EXCEPTION_H_
+#define _ESP_EXCEPTION_H_
+
+//
+// This exception stub is enabled only if exceptions are disabled in menuconfig
+//
+#if !defined(CONFIG_COMPILER_CXX_EXCEPTIONS) && defined (ASIO_NO_EXCEPTIONS)
+
+#include "esp_log.h"
+
+//
+// asio exception stub
+//
+namespace asio {
+namespace detail {
+template
+void throw_exception(const Exception& e)
+{
+ ESP_LOGE("esp32_asio_exception", "Caught exception: %s!", e.what());
+ abort();
+}
+}}
+#endif // CONFIG_COMPILER_CXX_EXCEPTIONS==1 && defined (ASIO_NO_EXCEPTIONS)
+
+#endif // _ESP_EXCEPTION_H_
diff --git a/components/asio/port/include/openssl_stub.hpp b/components/asio/port/include/openssl_stub.hpp
new file mode 100644
index 000000000..175879542
--- /dev/null
+++ b/components/asio/port/include/openssl_stub.hpp
@@ -0,0 +1,46 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+
+#pragma once
+
+//
+// Supply OpenSSL macros and flags for asio-ssl header files
+//
+#define OPENSSL_VERSION_NUMBER 0x10100001L
+
+#define SSL_R_SHORT_READ 219
+#define SSL_OP_ALL 0
+#define SSL_OP_SINGLE_DH_USE 0
+#define SSL_OP_NO_COMPRESSION 0
+
+#define SSL_OP_NO_SSLv2 0x01000000L
+#define SSL_OP_NO_SSLv3 0x02000000L
+#define SSL_OP_NO_TLSv1 0x04000000L
+
+#define SSL_VERIFY_NONE 0x00
+#define SSL_VERIFY_PEER 0x01
+#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
+#define SSL_VERIFY_CLIENT_ONCE 0x04
+
+//
+// Implement asio-ssl layer with these three classes in asio::ssl::mbedtls
+//
+namespace asio {
+namespace ssl {
+namespace mbedtls {
+
+class engine;
+class bio;
+class shared_ctx;
+} } } // namespace asio::ssl::mbedtls
+
+//
+// Supply OpenSSL types as aliases to mbedtls classes
+//
+using X509_STORE_CTX=void;
+using BIO=asio::ssl::mbedtls::bio;
+using SSL_CTX=asio::ssl::mbedtls::shared_ctx;
+using SSL=asio::ssl::mbedtls::engine;
diff --git a/components/asio/port/mbedtls/include/mbedtls_bio.hpp b/components/asio/port/mbedtls/include/mbedtls_bio.hpp
new file mode 100644
index 000000000..d5348a052
--- /dev/null
+++ b/components/asio/port/mbedtls/include/mbedtls_bio.hpp
@@ -0,0 +1,113 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
+
+#include "asio/ssl/context_base.hpp"
+#include "asio/ssl/context.hpp"
+#include "sdkconfig.h"
+
+namespace asio {
+namespace ssl {
+namespace mbedtls {
+
+class bio {
+ static constexpr int BIO_SIZE = CONFIG_ASIO_SSL_BIO_SIZE;
+ static constexpr int BIO_FLAGS_READ = 1;
+ static constexpr int BIO_FLAGS_WRITE = 2;
+
+public:
+ int write(const void *buf, int len)
+ {
+ if (buf == nullptr || len <= 0) {
+ // not an error, just empty operation (as in openssl/bio)
+ return 0;
+ }
+ int remaining = size_ - offset_;
+ if (remaining <= 0) {
+ flags_ |= BIO_FLAGS_WRITE;
+ return -1;
+ }
+ int len_to_write = len > remaining ? remaining : len;
+ std::memcpy(&data_[offset_], buf, len_to_write);
+ offset_ += len_to_write;
+ dlen_ = offset_;
+ if (len_to_write == len) {
+ flags_ &= ~BIO_FLAGS_WRITE;
+ }
+ return len_to_write;
+ }
+
+ int read(void *buf, int len)
+ {
+ if (buf == nullptr || len <= 0) {
+ // not an error, just empty operation (as in openssl/bio)
+ return 0;
+ }
+ int remaining = peer_->dlen_ - peer_->roffset_;
+ if (remaining <= 0) {
+ flags_ |= BIO_FLAGS_READ;
+ return -1;
+ }
+ int len_to_read = remaining > len ? len : remaining;
+ std::memcpy(buf, &peer_->data_[peer_->roffset_], len_to_read);
+ peer_->roffset_ += len_to_read;
+ if (len_to_read == len) {
+ flags_ &= ~BIO_FLAGS_READ;
+ }
+ if (peer_->offset_) {
+ // shift data back to the beginning of the buffer
+ std::memmove(&peer_->data_[0], &peer_->data_[peer_->roffset_], peer_->offset_ - peer_->roffset_);
+ peer_->offset_ -= peer_->roffset_;
+ peer_->roffset_ = 0;
+ peer_->dlen_ = peer_->offset_;
+ }
+ return len_to_read;
+ }
+
+ size_t wpending() const
+ {
+ return dlen_ - roffset_;
+ }
+
+ size_t ctrl_pending()
+ {
+ return peer_->dlen_ - peer_->roffset_;
+ }
+
+ bool should_write() const
+ {
+ return flags_ & BIO_FLAGS_WRITE;
+ }
+
+ bool should_read() const
+ {
+ return flags_ & BIO_FLAGS_READ;
+ }
+
+ static std::pair, std::shared_ptr> new_pair(const char* error_location)
+ {
+ auto b1 = std::shared_ptr(new (std::nothrow) bio);
+ auto b2 = std::shared_ptr(new (std::nothrow) bio);
+ if (b1 == nullptr || b2 == nullptr) {
+ throw_alloc_failure(error_location);
+ } else {
+ b1->peer_ = b2;
+ b2->peer_ = b1;
+ }
+ return std::make_pair(b1, b2);
+ }
+
+private:
+ std::array data_ {};
+ size_t size_ {BIO_SIZE};
+ std::shared_ptr peer_ {nullptr};
+ int dlen_ {0};
+ size_t offset_ {0};
+ size_t roffset_ {0};
+ size_t flags_ {0};
+};
+
+} } } // namespace asio::ssl::mbedtls
diff --git a/components/asio/port/mbedtls/include/mbedtls_context.hpp b/components/asio/port/mbedtls/include/mbedtls_context.hpp
new file mode 100644
index 000000000..27d135b5d
--- /dev/null
+++ b/components/asio/port/mbedtls/include/mbedtls_context.hpp
@@ -0,0 +1,105 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
+
+#include "asio/ssl/context_base.hpp"
+#include "asio/ssl/context.hpp"
+
+namespace asio {
+namespace error {
+
+const asio::error_category& get_mbedtls_category();
+} // namespace error
+
+namespace ssl {
+namespace mbedtls {
+
+void throw_alloc_failure(const char* location);
+
+const char *error_message(int error_code);
+
+enum class container {
+ CERT, CA_CERT, PRIVKEY
+};
+
+template
+inline T* create(const char * location, Args &&... args)
+{
+ T* t = new (std::nothrow) T(std::forward(args)...);
+ if (t == nullptr)
+ {
+ throw_alloc_failure(location);
+ }
+ return t;
+}
+
+class context {
+public:
+ explicit context(context_base::method m): method_(m), options_(0) {}
+
+ const unsigned char *data(container c) const
+ {
+ switch (c) {
+ case container::CERT:
+ return static_cast(cert_chain_.data());
+ case container::CA_CERT:
+ return static_cast(ca_cert_.data());
+ case container::PRIVKEY:
+ return static_cast(private_key_.data());
+ }
+ return nullptr;
+ }
+
+ std::size_t size(container c) const
+ {
+ switch (c) {
+ case container::CERT:
+ return cert_chain_.size();
+ case container::CA_CERT:
+ return ca_cert_.size();
+ case container::PRIVKEY:
+ return private_key_.size();
+ }
+ return 0;
+ }
+
+ context_base::method method_;
+ asio::ssl::context::options options_;
+ const_buffer cert_chain_;
+ const_buffer private_key_;
+ const_buffer ca_cert_;
+};
+
+/**
+ * @brief Wrapper class around SSL_CTX so we can easily create
+ * a shared pointer to the context without throwing the default exception.
+ * This is useful, as we can use asio::detail::throw_error for allocation errors.
+ */
+class shared_ctx {
+public:
+ static SSL_CTX *create(const char* location, context_base::method m)
+ {
+ auto wrapped = asio::ssl::mbedtls::create(location, m);
+ if (wrapped->ctx_ == nullptr)
+ {
+ throw_alloc_failure(location);
+ }
+ return wrapped;
+ }
+
+ std::shared_ptr get() const
+ {
+ return ctx_;
+ }
+
+ explicit shared_ctx(context_base::method m)
+ :ctx_(std::shared_ptr(new (std::nothrow) context(m))) { }
+
+private:
+ std::shared_ptr ctx_;
+};
+
+} } } // namespace asio::ssl::mbedtls
diff --git a/components/asio/port/mbedtls/include/mbedtls_engine.hpp b/components/asio/port/mbedtls/include/mbedtls_engine.hpp
new file mode 100644
index 000000000..a52ba459d
--- /dev/null
+++ b/components/asio/port/mbedtls/include/mbedtls_engine.hpp
@@ -0,0 +1,293 @@
+//
+// SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
+
+#include "mbedtls/ssl.h"
+#include "mbedtls/entropy.h"
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/error.h"
+#include "mbedtls/esp_debug.h"
+#include "esp_log.h"
+
+namespace asio {
+namespace ssl {
+namespace mbedtls {
+
+const char *error_message(int error_code)
+{
+ static char error_buf[100];
+ mbedtls_strerror(error_code, error_buf, sizeof(error_buf));
+ return error_buf;
+}
+
+void throw_alloc_failure(const char* location)
+{
+ asio::error_code ec( MBEDTLS_ERR_SSL_ALLOC_FAILED, asio::error::get_mbedtls_category());
+ asio::detail::throw_error(ec, location);
+}
+
+namespace error_codes {
+
+bool is_error(int ret)
+{
+ return ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE;
+}
+
+static bool want_write(int ret)
+{
+ return ret == MBEDTLS_ERR_SSL_WANT_WRITE;
+}
+
+static bool want_read(int ret)
+{
+ return ret == MBEDTLS_ERR_SSL_WANT_READ;
+}
+
+} // namespace error_codes
+
+enum rw_state {
+ IDLE, READING, WRITING, CLOSED
+};
+
+class engine {
+public:
+ explicit engine(std::shared_ptr ctx): ctx_(std::move(ctx)),
+ bio_(bio::new_pair("mbedtls-engine")), state_(IDLE), verify_mode_(0) {}
+
+ void set_verify_mode(asio::ssl::verify_mode mode)
+ {
+ verify_mode_ = mode;
+ }
+
+ bio* ext_bio() const
+ {
+ return bio_.second.get();
+ }
+
+ rw_state get_state() const
+ {
+ return state_;
+ }
+
+ int shutdown()
+ {
+ int ret = mbedtls_ssl_close_notify(&impl_.ssl_);
+ if (ret) {
+ impl::print_error("mbedtls_ssl_close_notify", ret);
+ }
+ state_ = CLOSED;
+ return ret;
+ }
+
+ int connect()
+ {
+ return handshake(true);
+ }
+
+ int accept()
+ {
+ return handshake(false);
+ }
+
+ int write(const void *buffer, int len)
+ {
+ int ret = impl_.write(buffer, len);
+ state_ = ret == len ? IDLE: WRITING;
+ return ret;
+ }
+
+ int read(void *buffer, int len)
+ {
+ int ret = impl_.read(buffer, len);
+ state_ = ret == len ? IDLE: READING;
+ return ret;
+ }
+
+private:
+ int handshake(bool is_client_not_server)
+ {
+ if (impl_.before_handshake()) {
+ impl_.configure(ctx_.get(), is_client_not_server, impl_verify_mode(is_client_not_server));
+ }
+ return do_handshake();
+ }
+
+ static int bio_read(void *ctx, unsigned char *buf, size_t len)
+ {
+ auto bio = static_cast(ctx);
+ int read = bio->read(buf, len);
+ if (read <= 0 && bio->should_read()) {
+ return MBEDTLS_ERR_SSL_WANT_READ;
+ }
+ return read;
+ }
+
+ static int bio_write(void *ctx, const unsigned char *buf, size_t len)
+ {
+ auto bio = static_cast(ctx);
+ int written = bio->write(buf, len);
+ if (written <= 0 && bio->should_write()) {
+ return MBEDTLS_ERR_SSL_WANT_WRITE;
+ }
+ return written;
+ }
+
+ int do_handshake()
+ {
+ int ret = 0;
+ mbedtls_ssl_set_bio(&impl_.ssl_, bio_.first.get(), bio_write, bio_read, nullptr);
+
+ while (impl_.ssl_.MBEDTLS_PRIVATE(state) != MBEDTLS_SSL_HANDSHAKE_OVER) {
+ ret = mbedtls_ssl_handshake_step(&impl_.ssl_);
+
+ if (ret != 0) {
+ if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+ impl::print_error("mbedtls_ssl_handshake_step", ret);
+ }
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
+ state_ = READING;
+ } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ state_ = WRITING;
+ }
+ break;
+ }
+ }
+ return ret;
+ }
+
+ // Converts OpenSSL verification mode to mbedtls enum
+ int impl_verify_mode(bool is_client_not_server) const
+ {
+ int mode = MBEDTLS_SSL_VERIFY_UNSET;
+ if (is_client_not_server) {
+ if (verify_mode_ & SSL_VERIFY_PEER)
+ mode = MBEDTLS_SSL_VERIFY_REQUIRED;
+ else if (verify_mode_ == SSL_VERIFY_NONE)
+ mode = MBEDTLS_SSL_VERIFY_NONE;
+ } else {
+ if (verify_mode_ & SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
+ mode = MBEDTLS_SSL_VERIFY_REQUIRED;
+ else if (verify_mode_ & SSL_VERIFY_PEER)
+ mode = MBEDTLS_SSL_VERIFY_OPTIONAL;
+ else if (verify_mode_ == SSL_VERIFY_NONE)
+ mode = MBEDTLS_SSL_VERIFY_NONE;
+ }
+ return mode;
+ }
+
+ struct impl {
+ static void print_error(const char* function, int error_code)
+ {
+ constexpr const char *TAG="mbedtls-engine-impl";
+ ESP_LOGE(TAG, "%s() returned -0x%04X", function, -error_code);
+ ESP_LOGI(TAG, "-0x%04X: %s", -error_code, error_message(error_code));
+ }
+
+ bool before_handshake() const
+ {
+ return ssl_.MBEDTLS_PRIVATE(state) == 0;
+ }
+
+ int write(const void *buffer, int len)
+ {
+ int ret = mbedtls_ssl_write(&ssl_, static_cast(buffer), len);
+ if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+ print_error("mbedtls_ssl_write", ret);
+ }
+ return ret;
+ }
+
+ int read(void *buffer, int len)
+ {
+ int ret = mbedtls_ssl_read(&ssl_, static_cast(buffer), len);
+ if (ret < 0 && ret != MBEDTLS_ERR_SSL_WANT_READ) {
+ print_error("mbedtls_ssl_read", ret);
+ }
+ return ret;
+ }
+
+ impl()
+ {
+ const unsigned char pers[] = "asio ssl";
+ mbedtls_ssl_init(&ssl_);
+ mbedtls_ssl_config_init(&conf_);
+ mbedtls_ctr_drbg_init(&ctr_drbg_);
+#ifdef CONFIG_MBEDTLS_DEBUG
+ mbedtls_esp_enable_debug_log(&conf_, CONFIG_MBEDTLS_DEBUG_LEVEL);
+#endif
+ mbedtls_entropy_init(&entropy_);
+ mbedtls_ctr_drbg_seed(&ctr_drbg_, mbedtls_entropy_func, &entropy_, pers, sizeof(pers));
+ mbedtls_x509_crt_init(&public_cert_);
+ mbedtls_pk_init(&pk_key_);
+ mbedtls_x509_crt_init(&ca_cert_);
+ }
+
+ bool configure(context *ctx, bool is_client_not_server, int mbedtls_verify_mode)
+ {
+ mbedtls_x509_crt_init(&public_cert_);
+ mbedtls_pk_init(&pk_key_);
+ mbedtls_x509_crt_init(&ca_cert_);
+ int ret = mbedtls_ssl_config_defaults(&conf_, is_client_not_server ? MBEDTLS_SSL_IS_CLIENT: MBEDTLS_SSL_IS_SERVER,
+ MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+ if (ret) {
+ print_error("mbedtls_ssl_config_defaults", ret);
+ return false;
+ }
+ mbedtls_ssl_conf_rng(&conf_, mbedtls_ctr_drbg_random, &ctr_drbg_);
+ mbedtls_ssl_conf_authmode(&conf_, mbedtls_verify_mode);
+ if (ctx->cert_chain_.size() > 0 && ctx->private_key_.size() > 0) {
+ ret = mbedtls_x509_crt_parse(&public_cert_, ctx->data(container::CERT), ctx->size(container::CERT));
+ if (ret < 0) {
+ print_error("mbedtls_x509_crt_parse", ret);
+ return false;
+ }
+ ret = mbedtls_pk_parse_key(&pk_key_, ctx->data(container::PRIVKEY), ctx->size(container::PRIVKEY),
+ nullptr, 0, mbedtls_ctr_drbg_random, &ctr_drbg_);
+ if (ret < 0) {
+ print_error("mbedtls_pk_parse_keyfile", ret);
+ return false;
+ }
+ ret = mbedtls_ssl_conf_own_cert(&conf_, &public_cert_, &pk_key_);
+ if (ret) {
+ print_error("mbedtls_ssl_conf_own_cert", ret);
+ return false;
+ }
+ }
+
+ if (ctx->ca_cert_.size() > 0) {
+ ret = mbedtls_x509_crt_parse(&ca_cert_, ctx->data(container::CA_CERT), ctx->size(container::CA_CERT));
+ if (ret < 0) {
+ print_error("mbedtls_x509_crt_parse", ret);
+ return false;
+ }
+ mbedtls_ssl_conf_ca_chain(&conf_, &ca_cert_, nullptr);
+ } else {
+ mbedtls_ssl_conf_ca_chain(&conf_, nullptr, nullptr);
+ }
+ ret = mbedtls_ssl_setup(&ssl_, &conf_);
+ if (ret) {
+ print_error("mbedtls_ssl_setup", ret);
+ return false;
+ }
+ return true;
+ }
+ mbedtls_ssl_context ssl_{};
+ mbedtls_entropy_context entropy_{};
+ mbedtls_ctr_drbg_context ctr_drbg_{};
+ mbedtls_ssl_config conf_{};
+ mbedtls_x509_crt public_cert_{};
+ mbedtls_pk_context pk_key_{};
+ mbedtls_x509_crt ca_cert_{};
+ };
+
+ impl impl_{};
+ std::shared_ptr ctx_;
+ std::pair, std::shared_ptr> bio_;
+ enum rw_state state_;
+ asio::ssl::verify_mode verify_mode_;
+};
+
+} } } // namespace asio::ssl::mbedtls
diff --git a/components/asio/port/mbedtls/include/mbedtls_error.hpp b/components/asio/port/mbedtls/include/mbedtls_error.hpp
new file mode 100644
index 000000000..083cf6c88
--- /dev/null
+++ b/components/asio/port/mbedtls/include/mbedtls_error.hpp
@@ -0,0 +1,55 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
+
+#include "asio/detail/config.hpp"
+#include "asio/ssl/error.hpp"
+#include "asio/ssl/detail/openssl_init.hpp"
+#include "mbedtls_context.hpp"
+
+namespace asio {
+namespace error {
+namespace detail {
+
+class mbedtls_category : public asio::error_category
+{
+public:
+ const char* name() const ASIO_ERROR_CATEGORY_NOEXCEPT
+ {
+ return "asio.ssl";
+ }
+
+ std::string message(int value) const
+ {
+ const char* s = asio::ssl::mbedtls::error_message(value);
+ return s ? s : "asio.mbedtls error";
+ }
+};
+
+} // namespace detail
+
+const asio::error_category& get_mbedtls_category()
+{
+ static detail::mbedtls_category instance;
+ return instance;
+}
+
+const asio::error_category& get_ssl_category()
+{
+ return asio::error::get_mbedtls_category();
+}
+
+} // namespace error
+
+namespace ssl {
+namespace error {
+
+const asio::error_category& get_stream_category()
+{
+ return asio::error::get_mbedtls_category();
+}
+
+} } } // namespace asio::ssl::error
diff --git a/components/asio/port/mbedtls/include/openssl/conf.h b/components/asio/port/mbedtls/include/openssl/conf.h
new file mode 100644
index 000000000..dc024544e
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/conf.h
@@ -0,0 +1,6 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
diff --git a/components/asio/port/mbedtls/include/openssl/dh.h b/components/asio/port/mbedtls/include/openssl/dh.h
new file mode 100644
index 000000000..dc024544e
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/dh.h
@@ -0,0 +1,6 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
diff --git a/components/asio/port/mbedtls/include/openssl/err.h b/components/asio/port/mbedtls/include/openssl/err.h
new file mode 100644
index 000000000..dc024544e
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/err.h
@@ -0,0 +1,6 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
diff --git a/components/asio/port/mbedtls/include/openssl/rsa.h b/components/asio/port/mbedtls/include/openssl/rsa.h
new file mode 100644
index 000000000..dc024544e
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/rsa.h
@@ -0,0 +1,6 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
diff --git a/components/asio/port/mbedtls/include/openssl/ssl.h b/components/asio/port/mbedtls/include/openssl/ssl.h
new file mode 100644
index 000000000..be147bc00
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/ssl.h
@@ -0,0 +1,8 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
+
+#include "openssl_stub.hpp"
diff --git a/components/asio/port/mbedtls/include/openssl/x509v3.h b/components/asio/port/mbedtls/include/openssl/x509v3.h
new file mode 100644
index 000000000..dc024544e
--- /dev/null
+++ b/components/asio/port/mbedtls/include/openssl/x509v3.h
@@ -0,0 +1,6 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+#pragma once
diff --git a/components/asio/port/mbedtls/src/mbedtls_context.cpp b/components/asio/port/mbedtls/src/mbedtls_context.cpp
new file mode 100644
index 000000000..36ba700dd
--- /dev/null
+++ b/components/asio/port/mbedtls/src/mbedtls_context.cpp
@@ -0,0 +1,115 @@
+//
+// SPDX-FileCopyrightText: 2005 Voipster / Indrek dot Juhani at voipster dot com
+// SPDX-FileCopyrightText: 2005-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+// SPDX-FileContributor: 2021 Espressif Systems (Shanghai) CO LTD
+//
+
+#include "asio/detail/config.hpp"
+#include "openssl_stub.hpp"
+#include
+#include "asio/detail/throw_error.hpp"
+#include "asio/error.hpp"
+#include "asio/ssl/context.hpp"
+#include "asio/ssl/error.hpp"
+#include "mbedtls_context.hpp"
+
+
+namespace asio {
+namespace ssl {
+
+
+context::context(context::method m)
+ : handle_(0)
+{
+ handle_ = mbedtls::shared_ctx::create("mbedtls-context", m);
+ set_options(no_compression);
+}
+
+context::context(context&& other)
+{
+ handle_ = other.handle_;
+ other.handle_ = 0;
+}
+
+context& context::operator=(context&& other)
+{
+ context tmp(ASIO_MOVE_CAST(context)(*this));
+ handle_ = other.handle_;
+ other.handle_ = 0;
+ return *this;
+}
+
+context::~context()
+{
+ delete handle_;
+}
+
+context::native_handle_type context::native_handle()
+{
+ return handle_;
+}
+
+void context::set_options(context::options o)
+{
+ asio::error_code ec;
+ set_options(o, ec);
+ asio::detail::throw_error(ec, "set_options");
+}
+
+ASIO_SYNC_OP_VOID context::set_options(
+ context::options o, asio::error_code& ec)
+{
+ handle_->get()->options_ = o;
+ ec = asio::error_code();
+ ASIO_SYNC_OP_VOID_RETURN(ec);
+}
+
+void context::add_certificate_authority(const const_buffer& ca)
+{
+ asio::error_code ec;
+ add_certificate_authority(ca, ec);
+ asio::detail::throw_error(ec, "add_certificate_authority");
+}
+
+ASIO_SYNC_OP_VOID context::add_certificate_authority(
+ const const_buffer& ca, asio::error_code& ec)
+{
+ handle_->get()->ca_cert_ = ca;
+ ASIO_SYNC_OP_VOID_RETURN(asio::error_code());
+}
+
+void context::use_certificate_chain(const const_buffer& chain)
+{
+ asio::error_code ec;
+ use_certificate_chain(chain, ec);
+ asio::detail::throw_error(ec, "use_certificate_chain");
+}
+
+ASIO_SYNC_OP_VOID context::use_certificate_chain(
+ const const_buffer& chain, asio::error_code& ec)
+{
+ handle_->get()->cert_chain_ = chain;
+ ASIO_SYNC_OP_VOID_RETURN(asio::error_code());
+}
+
+void context::use_private_key(
+ const const_buffer& private_key, context::file_format format)
+{
+ asio::error_code ec;
+ use_private_key(private_key, format, ec);
+ asio::detail::throw_error(ec, "use_private_key");
+}
+
+ASIO_SYNC_OP_VOID context::use_private_key(
+ const const_buffer& private_key, context::file_format format,
+ asio::error_code& ec)
+{
+ handle_->get()->private_key_ = private_key;
+ ASIO_SYNC_OP_VOID_RETURN(asio::error_code());
+}
+
+} // namespace ssl
+} // namespace asio
diff --git a/components/asio/port/mbedtls/src/mbedtls_engine.cpp b/components/asio/port/mbedtls/src/mbedtls_engine.cpp
new file mode 100644
index 000000000..c3ff91842
--- /dev/null
+++ b/components/asio/port/mbedtls/src/mbedtls_engine.cpp
@@ -0,0 +1,208 @@
+//
+// SPDX-FileCopyrightText: 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// SPDX-License-Identifier: BSL-1.0
+//
+// SPDX-FileContributor: 2021 Espressif Systems (Shanghai) CO LTD
+//
+
+#include "asio/detail/config.hpp"
+#include "openssl_stub.hpp"
+#include "asio/detail/throw_error.hpp"
+#include "asio/error.hpp"
+#include "asio/ssl/detail/engine.hpp"
+#include "asio/ssl/error.hpp"
+#include "asio/ssl/verify_context.hpp"
+#include "mbedtls_context.hpp"
+#include "mbedtls_bio.hpp"
+#include "mbedtls_error.hpp"
+#include "mbedtls_engine.hpp"
+
+
+namespace asio {
+namespace ssl {
+namespace detail {
+
+
+engine::engine(SSL_CTX* context)
+ : ssl_(nullptr)
+{
+ ssl_ = mbedtls::create("mbedtls-engine", context->get());
+}
+
+engine::~engine()
+{
+ delete ssl_;
+}
+
+SSL* engine::native_handle()
+{
+ return ssl_;
+}
+
+asio::error_code engine::set_verify_mode(
+ verify_mode v, asio::error_code& ec)
+{
+ ssl_->set_verify_mode(v);
+ return {};
+}
+
+engine::want engine::handshake(
+ stream_base::handshake_type type, asio::error_code& ec)
+{
+ return perform((type == asio::ssl::stream_base::client)
+ ? &engine::do_connect : &engine::do_accept, 0, 0, ec, 0);
+}
+
+engine::want engine::shutdown(asio::error_code& ec)
+{
+ return perform(&engine::do_shutdown, 0, 0, ec, 0);
+}
+
+engine::want engine::write(const asio::const_buffer& data,
+ asio::error_code& ec, std::size_t& bytes_transferred)
+{
+ if (data.size() == 0)
+ {
+ ec = asio::error_code();
+ return engine::want_nothing;
+ }
+
+ return perform(&engine::do_write,
+ const_cast(data.data()),
+ data.size(), ec, &bytes_transferred);
+}
+
+engine::want engine::read(const asio::mutable_buffer& data,
+ asio::error_code& ec, std::size_t& bytes_transferred)
+{
+ if (data.size() == 0)
+ {
+ ec = asio::error_code();
+ return engine::want_nothing;
+ }
+
+ return perform(&engine::do_read, data.data(),
+ data.size(), ec, &bytes_transferred);
+}
+
+asio::mutable_buffer engine::get_output(
+ const asio::mutable_buffer& data)
+{
+ int length = ssl_->ext_bio()->read(data.data(), static_cast(data.size()));
+
+ return asio::buffer(data,
+ length > 0 ? static_cast(length) : 0);
+}
+
+asio::const_buffer engine::put_input(
+ const asio::const_buffer& data)
+{
+ int length = ssl_->ext_bio()->write(data.data(), static_cast(data.size()));
+
+ return asio::buffer(data +
+ (length > 0 ? static_cast(length) : 0));
+}
+
+const asio::error_code& engine::map_error_code(
+ asio::error_code& ec) const
+{
+ // We only want to map the error::eof code.
+ if (ec != asio::error::eof)
+ return ec;
+
+ // If there's data yet to be read, it's an error.
+ if (ssl_->ext_bio()->wpending())
+ {
+ ec = asio::ssl::error::stream_truncated;
+ return ec;
+ }
+
+ // Otherwise, the peer should have negotiated a proper shutdown.
+ if (ssl_->shutdown() != 0)
+ {
+ ec = asio::ssl::error::stream_truncated;
+ }
+
+ return ec;
+}
+
+// This is a simplified implementation of a generic ssl io operation
+// original implementation using openssl's SSL object is in asio/include/asio/ssl/detail/impl/engine.ipp
+engine::want engine::perform(int (engine::* op)(void*, std::size_t),
+ void* data, std::size_t length, asio::error_code& ec,
+ std::size_t* bytes_transferred)
+{
+ std::size_t pending_output_before = ssl_->ext_bio()->ctrl_pending();
+ int result = (this->*op)(data, length);
+
+ std::size_t pending_output_after = ssl_->ext_bio()->ctrl_pending();
+
+ if (mbedtls::error_codes::is_error(result))
+ {
+ ec = asio::error_code(result, asio::error::get_mbedtls_category());
+ return pending_output_after > pending_output_before ? want_output : want_nothing;
+ }
+
+ if (result == 0)
+ {
+ return pending_output_after > pending_output_before
+ ? want_output : want_nothing;
+ }
+
+ if (result > 0 && bytes_transferred)
+ *bytes_transferred = static_cast(result);
+
+ if (mbedtls::error_codes::want_write(result))
+ {
+ ec = asio::error_code();
+ return want_output_and_retry;
+ }
+ else if (pending_output_after > pending_output_before)
+ {
+ ec = asio::error_code();
+ return result > 0 ? want_output : want_output_and_retry;
+ }
+ else if (mbedtls::error_codes::want_read(result))
+ {
+ ec = asio::error_code();
+ return want_input_and_retry;
+ }
+ else if (ssl_->get_state() == mbedtls::CLOSED)
+ {
+ ec = asio::error::eof;
+ return want_nothing;
+ }
+
+ ec = asio::error_code();
+ return want_nothing;
+}
+
+int engine::do_accept(void*, std::size_t)
+{
+ return ssl_->accept();
+}
+
+int engine::do_connect(void*, std::size_t)
+{
+ return ssl_->connect();
+}
+
+int engine::do_shutdown(void*, std::size_t)
+{
+ return ssl_->shutdown();
+}
+
+int engine::do_read(void* data, std::size_t length)
+{
+ return ssl_->read(data, length < INT_MAX ? static_cast(length) : INT_MAX);
+}
+
+int engine::do_write(void* data, std::size_t length)
+{
+ return ssl_->write(data, length < INT_MAX ? static_cast(length) : INT_MAX);
+}
+
+} // namespace detail
+} // namespace ssl
+} // namespace asio
diff --git a/components/asio/port/src/asio_ssl_impl.cpp b/components/asio/port/src/asio_ssl_impl.cpp
new file mode 100644
index 000000000..8f387dd7b
--- /dev/null
+++ b/components/asio/port/src/asio_ssl_impl.cpp
@@ -0,0 +1,20 @@
+//
+// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include "asio/detail/config.hpp"
+#include "asio/ssl/detail/openssl_init.hpp"
+
+namespace asio {
+namespace ssl {
+namespace detail {
+
+// No OpenSSL in this implementation, instance is nullptr
+asio::detail::shared_ptr openssl_init_base::instance()
+{
+ return nullptr;
+}
+
+} } } // namespace asio::ssl::detail