mirror of
https://github.com/espressif/esp-protocols.git
synced 2025-07-03 13:46:33 +02:00
Compare commits
77 Commits
mdns-v1.0.
...
asio-v1.0.
Author | SHA1 | Date | |
---|---|---|---|
055f051f53 | |||
ac7bf465d2 | |||
2cf7518114 | |||
057a5d2db8 | |||
63bff632df | |||
938ddf16b9 | |||
0c84c9750c | |||
952e5df477 | |||
e3c4391246 | |||
98138189c9 | |||
30dae8f7ed | |||
b76d3fbff9 | |||
f605fdd632 | |||
abbc8d9c5a | |||
a0297743dd | |||
3bcc46276a | |||
f0d0698582 | |||
91262baede | |||
cb2375827b | |||
cc0f2b3cf0 | |||
47d57a5b14 | |||
278b7bef3c | |||
a165452ca1 | |||
55f95fb790 | |||
9b76388163 | |||
0d1d73eb35 | |||
88d3eda156 | |||
622a360e97 | |||
4358c3ceab | |||
f00c3be139 | |||
c05558ba28 | |||
c0c1a65598 | |||
dab12309e2 | |||
789670e8c5 | |||
131613c5cc | |||
919091766d | |||
4e4ab0908d | |||
54e3a85983 | |||
98dfb691b6 | |||
ff7d214f9c | |||
ee6dbbfeaa | |||
a14331ad01 | |||
f3754a3683 | |||
c1176ccdcc | |||
df51e60ac0 | |||
9e83b1eb0e | |||
f82cbfde7b | |||
96d13293ab | |||
3b49d1f559 | |||
3e7591e92a | |||
de830e51d4 | |||
21c0878f0e | |||
a96c890f97 | |||
86d107635b | |||
57672d5add | |||
e8ea8786d2 | |||
142200c7ca | |||
6c47cfe30a | |||
0ae8c3ca33 | |||
5472d5c52f | |||
51a50db0fd | |||
e1660a1806 | |||
84f18bddfb | |||
609594a849 | |||
66e6d4cbf8 | |||
4868689111 | |||
f3ff98bb82 | |||
58a0b57e12 | |||
3fd4391c38 | |||
a16aab6979 | |||
1e0aefd72a | |||
ebf122bd36 | |||
5addf9e885 | |||
6dbfc69627 | |||
128c0a2d87 | |||
1f91d248f5 | |||
d56b5d90ea |
@ -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
|
||||
|
@ -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 }}
|
28
.github/workflows/build_asio.yml
vendored
Normal file
28
.github/workflows/build_asio.yml
vendored
Normal file
@ -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
|
8
.github/workflows/publish-docs-component.yml
vendored
8
.github/workflows/publish-docs-component.yml
vendored
@ -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 '<a href="esp_modem/index.html">esp-modem</a><br>' > index.html
|
||||
echo '<a href="esp_websocket_client/index.html">esp-websocket-client</a><br>' >> index.html
|
||||
echo '<a href="asio/index.html">ASIO</a><br>' >> index.html
|
||||
echo '<a href="mdns/en/index.html">mDNS_en</a><br>' >> index.html
|
||||
echo '<a href="mdns/zh_CN/index.html">mDNS_zh_CN</a><br>' >> 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 }}
|
||||
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "components/asio/asio"]
|
||||
path = components/asio/asio
|
||||
url = https://github.com/espressif/asio
|
@ -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)
|
||||
|
@ -1,4 +1,4 @@
|
||||
idf_component_register(SRCS "connect.c" "stdin_out.c" "addr_from_stdin.c"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES esp_netif driver
|
||||
PRIV_REQUIRES esp_netif driver esp_eth
|
||||
)
|
||||
|
@ -15,6 +15,7 @@ extern "C" {
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#define EXAMPLE_INTERFACE get_example_netif()
|
||||
|
44
components/asio/CMakeLists.txt
Normal file
44
components/asio/CMakeLists.txt
Normal file
@ -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()
|
36
components/asio/Kconfig
Normal file
36
components/asio/Kconfig
Normal file
@ -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
|
11
components/asio/README.md
Normal file
11
components/asio/README.md
Normal file
@ -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 <examples/..>`:
|
||||
|
||||
## Documentation
|
||||
|
||||
* View the full [html documentation](https://espressif.github.io/esp-protocols/asio/index.html)
|
1
components/asio/asio
Submodule
1
components/asio/asio
Submodule
Submodule components/asio/asio added at 58384fb6af
73
components/asio/docs/Doxyfile
Executable file
73
components/asio/docs/Doxyfile
Executable file
@ -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.
|
21
components/asio/docs/conf_common.py
Normal file
21
components/asio/docs/conf_common.py
Normal file
@ -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']
|
24
components/asio/docs/en/conf.py
Normal file
24
components/asio/docs/en/conf.py
Normal file
@ -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'
|
42
components/asio/docs/en/index.rst
Normal file
42
components/asio/docs/en/index.rst
Normal file
@ -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
|
27
components/asio/docs/generate_docs
Executable file
27
components/asio/docs/generate_docs
Executable file
@ -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 "<script type="text/javascript">
|
||||
window.onload =(function() {
|
||||
var myAnchor = document.getElementById('version-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'latest';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
var myAnchor = document.getElementById('target-select');
|
||||
var mySpan = document.createElement('input');
|
||||
mySpan.setAttribute('type', 'text');
|
||||
mySpan.setAttribute('maxLength', '10');
|
||||
mySpan.value = 'all targets';
|
||||
mySpan.setAttribute('disabled', true);
|
||||
myAnchor.parentNode.replaceChild(mySpan, myAnchor);
|
||||
|
||||
})();
|
||||
</script>" >> html/index.html
|
||||
|
27
components/asio/docs/zh_CN/conf.py
Normal file
27
components/asio/docs/zh_CN/conf.py
Normal file
@ -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'
|
1
components/asio/docs/zh_CN/index.rst
Normal file
1
components/asio/docs/zh_CN/index.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../en/api-reference/protocols/asio.rst
|
10
components/asio/examples/asio_chat/CMakeLists.txt
Normal file
10
components/asio/examples/asio_chat/CMakeLists.txt
Normal file
@ -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)
|
65
components/asio/examples/asio_chat/README.md
Normal file
65
components/asio/examples/asio_chat/README.md
Normal file
@ -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<CR>` 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.
|
29
components/asio/examples/asio_chat/example_test.py
Normal file
29
components/asio/examples/asio_chat/example_test.py
Normal file
@ -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()
|
2
components/asio/examples/asio_chat/main/CMakeLists.txt
Normal file
2
components/asio/examples/asio_chat/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "asio_chat.cpp"
|
||||
INCLUDE_DIRS ".")
|
39
components/asio/examples/asio_chat/main/Kconfig.projbuild
Normal file
39
components/asio/examples/asio_chat/main/Kconfig.projbuild
Normal file
@ -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
|
119
components/asio/examples/asio_chat/main/asio_chat.cpp
Normal file
119
components/asio/examples/asio_chat/main/asio_chat.cpp
Normal file
@ -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 <thread>
|
||||
#include <pthread.h>
|
||||
|
||||
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<std::mutex> 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());
|
||||
}
|
91
components/asio/examples/asio_chat/main/chat_message.hpp
Normal file
91
components/asio/examples/asio_chat/main/chat_message.hpp
Normal file
@ -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 <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
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<int>(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
|
126
components/asio/examples/asio_chat/main/client.hpp
Normal file
126
components/asio/examples/asio_chat/main/client.hpp
Normal file
@ -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 <deque>
|
||||
#include "asio.hpp"
|
||||
#include "chat_message.hpp"
|
||||
|
||||
typedef std::deque<chat_message> 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
|
202
components/asio/examples/asio_chat/main/server.hpp
Normal file
202
components/asio/examples/asio_chat/main/server.hpp
Normal file
@ -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 <list>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include "asio.hpp"
|
||||
#include "chat_message.hpp"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
typedef std::deque<chat_message> 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> 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<chat_participant_ptr> participants_;
|
||||
enum { max_recent_msgs = 100 };
|
||||
chat_message_queue recent_msgs_;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
|
||||
class chat_session
|
||||
: public chat_participant,
|
||||
public std::enable_shared_from_this<chat_session>
|
||||
{
|
||||
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<std::mutex> guard(server_ready);
|
||||
acceptor_.async_accept(
|
||||
[this](std::error_code ec, asio::ip::tcp::socket socket)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
std::make_shared<chat_session>(std::move(socket), room_)->start();
|
||||
}
|
||||
|
||||
do_accept();
|
||||
});
|
||||
}
|
||||
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
chat_room room_;
|
||||
};
|
||||
|
||||
#endif // CHAT_SERVER_HPP
|
6
components/asio/examples/asio_chat/sdkconfig.ci
Normal file
6
components/asio/examples/asio_chat/sdkconfig.ci
Normal file
@ -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"
|
2
components/asio/examples/asio_chat/sdkconfig.defaults
Normal file
2
components/asio/examples/asio_chat/sdkconfig.defaults
Normal file
@ -0,0 +1,2 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
10
components/asio/examples/async_request/CMakeLists.txt
Normal file
10
components/asio/examples/async_request/CMakeLists.txt
Normal file
@ -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)
|
52
components/asio/examples/async_request/README.md
Normal file
52
components/asio/examples/async_request/README.md
Normal file
@ -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.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "async_http_request.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,369 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* ASIO HTTP request example
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#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<AddressResolution> {
|
||||
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<class CompletionToken>
|
||||
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<Connection> {
|
||||
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<class CompletionToken>
|
||||
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<class DataType, class CompletionToken>
|
||||
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<class DataBuffer, class CompletionToken>
|
||||
void read_async(DataBuffer &&in_data, CompletionToken &&completion_handler)
|
||||
{
|
||||
asio::async_read(socket, in_data, asio::bind_executor(strand, completion_handler));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class CompletionToken>
|
||||
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<class DataIt>
|
||||
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<Session> {
|
||||
public:
|
||||
explicit Session(std::shared_ptr<Connection> connection_in) : connection(std::move(connection_in))
|
||||
{
|
||||
}
|
||||
|
||||
template<class CompletionToken>
|
||||
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<class CompletionToken>
|
||||
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<char, 2048> 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<asio::const_buffer, 2> send_data;
|
||||
std::shared_ptr<Connection> 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<class CompletionToken>
|
||||
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<AddressResolution>(context)->resolve(request.host(), request.service_port(),
|
||||
[&context, &request, completion_handler](std::shared_ptr<AddressResolution> resolver, tcp::resolver::results_type results) {
|
||||
/* After resolution we create a Connection.
|
||||
* The completion handler gets a shared_ptr<Connection> to receive the connection, once the
|
||||
* connection process is complete.
|
||||
*/
|
||||
std::make_shared<Connection>(context)->start(results,
|
||||
[&request, completion_handler](std::shared_ptr<Connection> connection) {
|
||||
// Now we create a HTTP::Session and inject the necessary connection.
|
||||
std::make_shared<Session>(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<Http::Session> 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());
|
||||
}
|
10
components/asio/examples/socks4/CMakeLists.txt
Normal file
10
components/asio/examples/socks4/CMakeLists.txt
Normal file
@ -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)
|
73
components/asio/examples/socks4/README.md
Normal file
73
components/asio/examples/socks4/README.md
Normal file
@ -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).
|
||||
|
2
components/asio/examples/socks4/main/CMakeLists.txt
Normal file
2
components/asio/examples/socks4/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "socks4.cpp"
|
||||
INCLUDE_DIRS ".")
|
16
components/asio/examples/socks4/main/Kconfig.projbuild
Normal file
16
components/asio/examples/socks4/main/Kconfig.projbuild
Normal file
@ -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
|
393
components/asio/examples/socks4/main/socks4.cpp
Normal file
393
components/asio/examples/socks4/main/socks4.cpp
Normal file
@ -0,0 +1,393 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
*
|
||||
* ASIO Socks4 example
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
#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<AddressResolution> {
|
||||
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<class CompletionToken>
|
||||
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<Connection> {
|
||||
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<class CompletionToken>
|
||||
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<class DataType, class CompletionToken>
|
||||
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<class DataBuffer, class CompletionToken>
|
||||
void read_async(DataBuffer &&in_data, CompletionToken &&completion_handler)
|
||||
{
|
||||
asio::async_read(socket, in_data, asio::bind_executor(strand, completion_handler));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<class CompletionToken>
|
||||
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<class CompletionToken>
|
||||
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<AddressResolution>(context)->resolve(proxy, proxy_port,
|
||||
[&context, host, port, completion_handler](std::shared_ptr<AddressResolution> 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<AddressResolution> resolver, tcp::resolver::results_type host_resolution) {
|
||||
// Make connection with the proxy
|
||||
ESP_LOGI(TAG, "Startig Proxy Connection");
|
||||
std::make_shared<Connection>(context)->start(proxy_resolution,
|
||||
[resolver, host_resolution, completion_handler](std::shared_ptr<Connection> connection) {
|
||||
auto connect_data = std::make_shared<ConnectionData>(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<class DataIt>
|
||||
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<Session> {
|
||||
public:
|
||||
explicit Session(std::shared_ptr<Connection> connection_in) : connection(std::move(connection_in))
|
||||
{
|
||||
}
|
||||
|
||||
template<class CompletionToken>
|
||||
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<class CompletionToken>
|
||||
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<char, 2048> 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<asio::const_buffer, 2> send_data;
|
||||
std::shared_ptr<Connection> 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> connection) {
|
||||
// Now we create a HTTP::Session and inject the necessary connection.
|
||||
std::make_shared<Http::Session>(connection)->send_request(request, [](std::shared_ptr<Http::Session> 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());
|
||||
}
|
143
components/asio/examples/socks4/main/socks4.hpp
Normal file
143
components/asio/examples/socks4/main/socks4.hpp
Normal file
@ -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 <array>
|
||||
#include <string>
|
||||
#include <asio/buffer.hpp>
|
||||
#include <asio/ip/tcp.hpp>
|
||||
|
||||
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<asio::const_buffer, 7> 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<asio::mutable_buffer, 5> 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
|
3
components/asio/examples/socks4/sdkconfig.defaults
Normal file
3
components/asio/examples/socks4/sdkconfig.defaults
Normal file
@ -0,0 +1,3 @@
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_RTTI=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
|
11
components/asio/examples/ssl_client_server/CMakeLists.txt
Normal file
11
components/asio/examples/ssl_client_server/CMakeLists.txt
Normal file
@ -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)
|
88
components/asio/examples/ssl_client_server/README.md
Normal file
88
components/asio/examples/ssl_client_server/README.md
Normal file
@ -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.
|
16
components/asio/examples/ssl_client_server/example_test.py
Normal file
16
components/asio/examples/ssl_client_server/example_test.py
Normal file
@ -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()
|
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "asio_ssl_main.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES ca.crt server.key srv.crt)
|
@ -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
|
@ -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 <string>
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_event.h"
|
||||
#include "nvs_flash.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#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<tcp::socket> 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<Session> {
|
||||
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<tcp::socket> 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<Session>(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<std::thread> 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();
|
||||
}
|
||||
|
||||
}
|
22
components/asio/examples/ssl_client_server/main/ca.crt
Normal file
22
components/asio/examples/ssl_client_server/main/ca.crt
Normal file
@ -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-----
|
27
components/asio/examples/ssl_client_server/main/server.key
Normal file
27
components/asio/examples/ssl_client_server/main/server.key
Normal file
@ -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-----
|
18
components/asio/examples/ssl_client_server/main/srv.crt
Normal file
18
components/asio/examples/ssl_client_server/main/srv.crt
Normal file
@ -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-----
|
@ -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,
|
|
6
components/asio/examples/ssl_client_server/sdkconfig.ci
Normal file
6
components/asio/examples/ssl_client_server/sdkconfig.ci
Normal file
@ -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
|
@ -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
|
10
components/asio/examples/tcp_echo_server/CMakeLists.txt
Normal file
10
components/asio/examples/tcp_echo_server/CMakeLists.txt
Normal file
@ -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)
|
22
components/asio/examples/tcp_echo_server/README.md
Normal file
22
components/asio/examples/tcp_echo_server/README.md
Normal file
@ -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.
|
@ -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()
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "echo_server.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
108
components/asio/examples/tcp_echo_server/main/echo_server.cpp
Normal file
108
components/asio/examples/tcp_echo_server/main/echo_server.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include "asio.hpp"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#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<session>
|
||||
{
|
||||
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<session>(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();
|
||||
}
|
@ -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
|
10
components/asio/examples/udp_echo_server/CMakeLists.txt
Normal file
10
components/asio/examples/udp_echo_server/CMakeLists.txt
Normal file
@ -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)
|
22
components/asio/examples/udp_echo_server/README.md
Normal file
22
components/asio/examples/udp_echo_server/README.md
Normal file
@ -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.
|
@ -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()
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "udp_echo_server.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
@ -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 <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
#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();
|
||||
}
|
@ -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
|
5
components/asio/idf_component.yml
Normal file
5
components/asio/idf_component.yml
Normal file
@ -0,0 +1,5 @@
|
||||
version: "1.0.1"
|
||||
description: ASIO
|
||||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
45
components/asio/port/include/esp_asio_config.h
Normal file
45
components/asio/port/include/esp_asio_config.h
Normal file
@ -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 <sdkconfig.h>
|
||||
# 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_
|
30
components/asio/port/include/esp_exception.h
Normal file
30
components/asio/port/include/esp_exception.h
Normal file
@ -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 <typename Exception>
|
||||
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_
|
46
components/asio/port/include/openssl_stub.hpp
Normal file
46
components/asio/port/include/openssl_stub.hpp
Normal file
@ -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;
|
113
components/asio/port/mbedtls/include/mbedtls_bio.hpp
Normal file
113
components/asio/port/mbedtls/include/mbedtls_bio.hpp
Normal file
@ -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<bio>, std::shared_ptr<bio>> new_pair(const char* error_location)
|
||||
{
|
||||
auto b1 = std::shared_ptr<bio>(new (std::nothrow) bio);
|
||||
auto b2 = std::shared_ptr<bio>(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<uint8_t, BIO_SIZE> data_ {};
|
||||
size_t size_ {BIO_SIZE};
|
||||
std::shared_ptr<bio> peer_ {nullptr};
|
||||
int dlen_ {0};
|
||||
size_t offset_ {0};
|
||||
size_t roffset_ {0};
|
||||
size_t flags_ {0};
|
||||
};
|
||||
|
||||
} } } // namespace asio::ssl::mbedtls
|
105
components/asio/port/mbedtls/include/mbedtls_context.hpp
Normal file
105
components/asio/port/mbedtls/include/mbedtls_context.hpp
Normal file
@ -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 <typename T, typename... Args>
|
||||
inline T* create(const char * location, Args &&... args)
|
||||
{
|
||||
T* t = new (std::nothrow) T(std::forward<Args>(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<const unsigned char *>(cert_chain_.data());
|
||||
case container::CA_CERT:
|
||||
return static_cast<const unsigned char *>(ca_cert_.data());
|
||||
case container::PRIVKEY:
|
||||
return static_cast<const unsigned char *>(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<shared_ctx>(location, m);
|
||||
if (wrapped->ctx_ == nullptr)
|
||||
{
|
||||
throw_alloc_failure(location);
|
||||
}
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
std::shared_ptr<mbedtls::context> get() const
|
||||
{
|
||||
return ctx_;
|
||||
}
|
||||
|
||||
explicit shared_ctx(context_base::method m)
|
||||
:ctx_(std::shared_ptr<context>(new (std::nothrow) context(m))) { }
|
||||
|
||||
private:
|
||||
std::shared_ptr<mbedtls::context> ctx_;
|
||||
};
|
||||
|
||||
} } } // namespace asio::ssl::mbedtls
|
293
components/asio/port/mbedtls/include/mbedtls_engine.hpp
Normal file
293
components/asio/port/mbedtls/include/mbedtls_engine.hpp
Normal file
@ -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<context> 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<BIO*>(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<BIO*>(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<const unsigned char *>(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<unsigned char *>(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<context> ctx_;
|
||||
std::pair<std::shared_ptr<bio>, std::shared_ptr<bio>> bio_;
|
||||
enum rw_state state_;
|
||||
asio::ssl::verify_mode verify_mode_;
|
||||
};
|
||||
|
||||
} } } // namespace asio::ssl::mbedtls
|
55
components/asio/port/mbedtls/include/mbedtls_error.hpp
Normal file
55
components/asio/port/mbedtls/include/mbedtls_error.hpp
Normal file
@ -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
|
6
components/asio/port/mbedtls/include/openssl/conf.h
Normal file
6
components/asio/port/mbedtls/include/openssl/conf.h
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
6
components/asio/port/mbedtls/include/openssl/dh.h
Normal file
6
components/asio/port/mbedtls/include/openssl/dh.h
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
6
components/asio/port/mbedtls/include/openssl/err.h
Normal file
6
components/asio/port/mbedtls/include/openssl/err.h
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
6
components/asio/port/mbedtls/include/openssl/rsa.h
Normal file
6
components/asio/port/mbedtls/include/openssl/rsa.h
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
8
components/asio/port/mbedtls/include/openssl/ssl.h
Normal file
8
components/asio/port/mbedtls/include/openssl/ssl.h
Normal file
@ -0,0 +1,8 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "openssl_stub.hpp"
|
6
components/asio/port/mbedtls/include/openssl/x509v3.h
Normal file
6
components/asio/port/mbedtls/include/openssl/x509v3.h
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
#pragma once
|
115
components/asio/port/mbedtls/src/mbedtls_context.cpp
Normal file
115
components/asio/port/mbedtls/src/mbedtls_context.cpp
Normal file
@ -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 <cstring>
|
||||
#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
|
208
components/asio/port/mbedtls/src/mbedtls_engine.cpp
Normal file
208
components/asio/port/mbedtls/src/mbedtls_engine.cpp
Normal file
@ -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>("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<void*>(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<int>(data.size()));
|
||||
|
||||
return asio::buffer(data,
|
||||
length > 0 ? static_cast<std::size_t>(length) : 0);
|
||||
}
|
||||
|
||||
asio::const_buffer engine::put_input(
|
||||
const asio::const_buffer& data)
|
||||
{
|
||||
int length = ssl_->ext_bio()->write(data.data(), static_cast<int>(data.size()));
|
||||
|
||||
return asio::buffer(data +
|
||||
(length > 0 ? static_cast<std::size_t>(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<std::size_t>(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<int>(length) : INT_MAX);
|
||||
}
|
||||
|
||||
int engine::do_write(void* data, std::size_t length)
|
||||
{
|
||||
return ssl_->write(data, length < INT_MAX ? static_cast<int>(length) : INT_MAX);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ssl
|
||||
} // namespace asio
|
20
components/asio/port/src/asio_ssl_impl.cpp
Normal file
20
components/asio/port/src/asio_ssl_impl.cpp
Normal file
@ -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::do_init> openssl_init_base::instance()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} } } // namespace asio::ssl::detail
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <esp_console.h>
|
||||
|
@ -87,10 +87,10 @@ extern "C" void app_main(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dce->set_mode(esp_modem::modem_mode::CMUX_MODE) && dce->set_mode(esp_modem::modem_mode::DATA_MODE)) {
|
||||
if (dce->set_mode(esp_modem::modem_mode::CMUX_MODE)) {
|
||||
std::cout << "Modem has correctly entered multiplexed command/data mode" << std::endl;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
|
||||
ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: "0.1.16"
|
||||
version: "0.1.17"
|
||||
description: esp modem
|
||||
url: https://github.com/espressif/esp-protocols/tree/master/components/esp_modem
|
||||
dependencies:
|
||||
|
@ -0,0 +1,50 @@
|
||||
// Copyright 2022 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
/**
|
||||
* Common unique buffer, which is transferable between DTE and CMUX
|
||||
*
|
||||
*/
|
||||
struct unique_buffer {
|
||||
explicit unique_buffer(size_t size);
|
||||
unique_buffer (unique_buffer const&) = delete;
|
||||
unique_buffer& operator=(unique_buffer const&) = delete;
|
||||
unique_buffer(unique_buffer&& other) noexcept
|
||||
{
|
||||
data = std::move(other.data);
|
||||
size = other.size;
|
||||
consumed = 0;
|
||||
}
|
||||
unique_buffer& operator=(unique_buffer&& other) noexcept
|
||||
{
|
||||
if (&other == this) {
|
||||
return *this;
|
||||
}
|
||||
data = std::move(other.data);
|
||||
size = other.size;
|
||||
consumed = 0;
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] uint8_t *get() const { return data.get(); }
|
||||
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
size_t size{};
|
||||
size_t consumed{};
|
||||
};
|
||||
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_modem_terminal.hpp"
|
||||
#include "cxx_include/esp_modem_buffer.hpp"
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
@ -54,8 +55,8 @@ class CMuxInstance;
|
||||
*/
|
||||
class CMux {
|
||||
public:
|
||||
explicit CMux(std::unique_ptr<Terminal> t, std::unique_ptr<uint8_t[]> b, size_t buff_size):
|
||||
term(std::move(t)), payload_start(nullptr), total_payload_size(0), buffer_size(buff_size), buffer(std::move(b)) {}
|
||||
explicit CMux(std::shared_ptr<Terminal> t, unique_buffer&& b):
|
||||
term(std::move(t)), payload_start(nullptr), total_payload_size(0), buffer(std::move(b)) {}
|
||||
~CMux() = default;
|
||||
|
||||
/**
|
||||
@ -64,6 +65,19 @@ public:
|
||||
*/
|
||||
[[nodiscard]] bool init();
|
||||
|
||||
/**
|
||||
* @brief Closes and deinits CMux protocol
|
||||
* @return true on success
|
||||
*/
|
||||
[[nodiscard]] bool deinit();
|
||||
|
||||
/**
|
||||
* @brief Ejects the attached terminal and buffer,
|
||||
* so they could be used as traditional command/data DTE's
|
||||
* @return pair of the original terminal and buffer
|
||||
*/
|
||||
std::pair<std::shared_ptr<Terminal>, unique_buffer> detach();
|
||||
|
||||
/**
|
||||
* @brief Sets read callback for the appropriate terminal
|
||||
* @param inst Index of the terminal
|
||||
@ -84,7 +98,8 @@ private:
|
||||
static uint8_t fcs_crc(const uint8_t frame[6]); /*!< Utility to calculate FCS CRC */
|
||||
void data_available(uint8_t *data, size_t len); /*!< Called when valid data available */
|
||||
void send_sabm(size_t i); /*!< Sending initial SABM */
|
||||
bool on_cmux(uint8_t *data, size_t len); /*!< Called from terminal layer when raw CMUX protocol data available */
|
||||
void send_disconnect(size_t i); /*!< Sending closing request for each virtual or control terminal */
|
||||
bool on_cmux_data(uint8_t *data, size_t len); /*!< Called from terminal layer when raw CMUX protocol data available */
|
||||
|
||||
struct CMuxFrame; /*!< Forward declare the Frame struct, used in protocol decoders */
|
||||
/**
|
||||
@ -100,7 +115,7 @@ private:
|
||||
bool on_footer(CMuxFrame &frame);
|
||||
|
||||
std::function<bool(uint8_t *data, size_t len)> read_cb[MAX_TERMINALS_NUM]; /*!< Function pointers to read callbacks */
|
||||
std::unique_ptr<Terminal> term; /*!< The original terminal */
|
||||
std::shared_ptr<Terminal> term; /*!< The original terminal */
|
||||
cmux_state state; /*!< CMux protocol state */
|
||||
|
||||
/**
|
||||
@ -117,10 +132,9 @@ private:
|
||||
int sabm_ack;
|
||||
|
||||
/**
|
||||
* Processing buffer size and pointer
|
||||
* Processing unique buffer (reused and transferred from it's parent DTE)
|
||||
*/
|
||||
size_t buffer_size;
|
||||
std::unique_ptr<uint8_t[]> buffer;
|
||||
unique_buffer buffer;
|
||||
|
||||
Lock lock;
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
modem_mode get();
|
||||
|
||||
private:
|
||||
bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
|
||||
modem_mode mode;
|
||||
|
||||
};
|
||||
|
@ -79,8 +79,17 @@ public:
|
||||
}
|
||||
return true;
|
||||
} else if (mode == modem_mode::COMMAND_MODE) {
|
||||
Task::Delay(1000); // Mandatory 1s pause
|
||||
return set_command_mode() == command_result::OK;
|
||||
Task::Delay(1000); // Mandatory 1s pause before
|
||||
int retry = 0;
|
||||
while (retry++ < 3) {
|
||||
if (set_command_mode() == command_result::OK)
|
||||
return true;
|
||||
Task::Delay(1000); // Mandatory 1s pause after escape
|
||||
if (sync() == command_result::OK)
|
||||
return true;
|
||||
Task::Delay(1000); // Mandatory 1s pause before escape
|
||||
}
|
||||
return false;
|
||||
} else if (mode == modem_mode::CMUX_MODE) {
|
||||
return set_cmux() == command_result::OK;
|
||||
}
|
||||
|
@ -15,18 +15,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include "cxx_include/esp_modem_primitives.hpp"
|
||||
#include "cxx_include/esp_modem_terminal.hpp"
|
||||
#include "cxx_include/esp_modem_cmux.hpp"
|
||||
#include "cxx_include/esp_modem_types.hpp"
|
||||
#include "cxx_include/esp_modem_buffer.hpp"
|
||||
|
||||
struct esp_modem_dte_config;
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
class CMux;
|
||||
|
||||
/**
|
||||
* @defgroup ESP_MODEM_DTE
|
||||
* @brief Definition of DTE and related classes
|
||||
@ -94,21 +96,27 @@ public:
|
||||
*/
|
||||
command_result command(const std::string &command, got_line_cb got_line, uint32_t time_ms, char separator) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Allows for locking the DTE
|
||||
*/
|
||||
void lock() { internal_lock.lock(); }
|
||||
void unlock() { internal_lock.unlock(); }
|
||||
friend class Scoped<DTE>; /*!< Declaring "Scoped<DTE> lock(dte)" locks this instance */
|
||||
private:
|
||||
static const size_t GOT_LINE = SignalGroup::bit0; /*!< Bit indicating response available */
|
||||
|
||||
[[nodiscard]] bool setup_cmux(); /*!< Internal setup of CMUX mode */
|
||||
[[nodiscard]] bool setup_cmux(); /*!< Internal setup of CMUX mode */
|
||||
[[nodiscard]] bool exit_cmux(); /*!< Exit of CMUX mode */
|
||||
|
||||
Lock lock{}; /*!< Locks DTE operations */
|
||||
size_t buffer_size; /*!< Size of available DTE buffer */
|
||||
size_t consumed; /*!< Indication of already processed portion in DTE buffer */
|
||||
std::unique_ptr<uint8_t[]> buffer; /*!< DTE buffer */
|
||||
std::unique_ptr<Terminal> term; /*!< Primary terminal for this DTE */
|
||||
Terminal *command_term; /*!< Reference to the terminal used for sending commands */
|
||||
std::unique_ptr<Terminal> other_term; /*!< Secondary terminal for this DTE */
|
||||
modem_mode mode; /*!< DTE operation mode */
|
||||
Lock internal_lock{}; /*!< Locks DTE operations */
|
||||
unique_buffer buffer; /*!< DTE buffer */
|
||||
std::shared_ptr<CMux> cmux_term; /*!< Primary terminal for this DTE */
|
||||
std::shared_ptr<Terminal> command_term; /*!< Reference to the terminal used for sending commands */
|
||||
std::shared_ptr<Terminal> data_term; /*!< Secondary terminal for this DTE */
|
||||
modem_mode mode; /*!< DTE operation mode */
|
||||
SignalGroup signal; /*!< Event group used to signal request-response operations */
|
||||
std::function<bool(uint8_t *data, size_t len)> on_data; /*!< on data callback for current terminal */
|
||||
std::function<bool(uint8_t *data, size_t len)> on_data; /*!< on data callback for current terminal */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -46,7 +46,7 @@ private:
|
||||
using TaskT = TaskHandle_t;
|
||||
using SignalT = EventGroupHandle_t;
|
||||
#else
|
||||
using Lock = std::mutex;
|
||||
using Lock = std::recursive_mutex;
|
||||
struct SignalGroupInternal;
|
||||
using SignalT = std::unique_ptr<SignalGroupInternal>;
|
||||
using TaskT = std::thread;
|
||||
|
@ -39,6 +39,7 @@ typedef enum esp_modem_dce_mode
|
||||
{
|
||||
ESP_MODEM_MODE_COMMAND, /**< Default mode after modem startup, used for sending AT commands */
|
||||
ESP_MODEM_MODE_DATA, /**< Used for switching to PPP mode for the modem to connect to a network */
|
||||
ESP_MODEM_MODE_CMUX, /**< Multiplexed terminal mode */
|
||||
} esp_modem_dce_mode_t;
|
||||
|
||||
/**
|
||||
|
@ -125,6 +125,13 @@ extern "C" esp_err_t esp_modem_set_mode(esp_modem_dce_t *dce_wrap, esp_modem_dce
|
||||
dce_wrap->dce->set_data();
|
||||
} else if (mode == ESP_MODEM_MODE_COMMAND) {
|
||||
dce_wrap->dce->exit_data();
|
||||
} else if (mode == ESP_MODEM_MODE_CMUX) {
|
||||
if (dce_wrap->dce->set_mode(modem_mode::CMUX_MODE) &&
|
||||
// automatically switch to data mode for the primary terminal
|
||||
dce_wrap->dce->set_mode(modem_mode::DATA_MODE)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
} else {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
@ -78,6 +78,21 @@ uint8_t CMux::fcs_crc(const uint8_t frame[6])
|
||||
return crc;
|
||||
}
|
||||
|
||||
void CMux::send_disconnect(size_t i)
|
||||
{
|
||||
if (i == 0) { // control terminal
|
||||
uint8_t frame[] = {
|
||||
SOF_MARKER, 0x3, 0xFF, 0x5, 0xC3, 0x1, 0xE7, SOF_MARKER };
|
||||
term->write(frame, 8);
|
||||
} else { // separate virtual terminal
|
||||
uint8_t frame[] = {
|
||||
SOF_MARKER, 0x3, FT_DISC | PF, 0x1, 0, SOF_MARKER };
|
||||
frame[1] |= i << 2;
|
||||
frame[4] = 0xFF - fcs_crc(frame);
|
||||
term->write(frame, sizeof(frame));
|
||||
}
|
||||
}
|
||||
|
||||
void CMux::send_sabm(size_t i)
|
||||
{
|
||||
uint8_t frame[6];
|
||||
@ -127,6 +142,9 @@ void CMux::data_available(uint8_t *data, size_t len)
|
||||
read_cb[virtual_term](payload_start, total_payload_size);
|
||||
#endif
|
||||
}
|
||||
} else if (type == 0xFF && dlci == 0) { // notify the internal DISC command
|
||||
Scoped<Lock> l(lock);
|
||||
sabm_ack = dlci;
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,10 +205,22 @@ bool CMux::on_header(CMuxFrame &frame)
|
||||
}
|
||||
size_t payload_offset = std::min(frame.len, 4 - frame_header_offset);
|
||||
memcpy(frame_header + frame_header_offset, frame.ptr, payload_offset);
|
||||
frame_header_offset += payload_offset;
|
||||
if ((frame_header[3] & 1) == 0) {
|
||||
if (frame_header_offset + frame.len <= 4) {
|
||||
frame_header_offset += frame.len;
|
||||
return false; // need read more
|
||||
}
|
||||
payload_offset = std::min(frame.len, 5 - frame_header_offset);
|
||||
memcpy(frame_header + frame_header_offset, frame.ptr, payload_offset);
|
||||
payload_len = frame_header[4] << 7;
|
||||
frame_header_offset += payload_offset - 1; // rewind frame_header back to hold only 6 bytes size
|
||||
} else {
|
||||
payload_len = 0;
|
||||
frame_header_offset += payload_offset;
|
||||
}
|
||||
dlci = frame_header[1] >> 2;
|
||||
type = frame_header[2];
|
||||
payload_len = (frame_header[3] >> 1);
|
||||
payload_len += (frame_header[3] >> 1);
|
||||
frame.advance(payload_offset);
|
||||
state = cmux_state::PAYLOAD;
|
||||
return true;
|
||||
@ -242,11 +272,11 @@ bool CMux::on_footer(CMuxFrame &frame)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMux::on_cmux(uint8_t *data, size_t actual_len)
|
||||
bool CMux::on_cmux_data(uint8_t *data, size_t actual_len)
|
||||
{
|
||||
if (!data) {
|
||||
#ifdef DEFRAGMENT_CMUX_PAYLOAD
|
||||
auto data_to_read = buffer_size - 128; // keep 128 (max CMUX payload) backup buffer)
|
||||
auto data_to_read = buffer.size - 128; // keep 128 (max CMUX payload) backup buffer)
|
||||
if (payload_start) {
|
||||
data = payload_start + total_payload_size;
|
||||
data_to_read = payload_len + 2;
|
||||
@ -293,12 +323,48 @@ bool CMux::on_cmux(uint8_t *data, size_t actual_len)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMux::deinit()
|
||||
{
|
||||
int timeout = 0;
|
||||
sabm_ack = -1;
|
||||
// First disconnect all (2) virtual terminals
|
||||
for (size_t i = 1; i < 3; i++) {
|
||||
send_disconnect(i);
|
||||
while (true) {
|
||||
usleep(10'000);
|
||||
Scoped<Lock> l(lock);
|
||||
if (sabm_ack == i) {
|
||||
sabm_ack = -1;
|
||||
break;
|
||||
}
|
||||
if (timeout++ > 100) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
sabm_ack = -1;
|
||||
// Then disconnect the control terminal
|
||||
send_disconnect(0);
|
||||
while (true) {
|
||||
usleep(10'000);
|
||||
Scoped<Lock> l(lock);
|
||||
if (sabm_ack == 0) {
|
||||
break;
|
||||
}
|
||||
if (timeout++ > 100) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
term->set_read_cb(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMux::init()
|
||||
{
|
||||
frame_header_offset = 0;
|
||||
state = cmux_state::INIT;
|
||||
term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||
this->on_cmux(data, len);
|
||||
this->on_cmux_data(data, len);
|
||||
return false;
|
||||
});
|
||||
|
||||
@ -358,3 +424,8 @@ void CMux::set_read_cb(int inst, std::function<bool(uint8_t *, size_t)> f)
|
||||
read_cb[inst] = std::move(f);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<Terminal>, unique_buffer> CMux::detach()
|
||||
{
|
||||
return std::make_pair(std::move(term), std::move(buffer));
|
||||
}
|
||||
|
@ -12,7 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <list>
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "cxx_include/esp_modem_dce.hpp"
|
||||
@ -20,33 +22,77 @@
|
||||
|
||||
namespace esp_modem {
|
||||
|
||||
|
||||
/**
|
||||
* Set mode while the entire DTE is locked
|
||||
*/
|
||||
bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
|
||||
{
|
||||
Scoped<DTE> lock(*dte);
|
||||
return set_unsafe(dte, device, netif, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* state machine:
|
||||
*
|
||||
* COMMAND_MODE <----> DATA_MODE
|
||||
* COMMAND_MODE <----> CMUX_MODE
|
||||
*
|
||||
* UNDEF <----> any
|
||||
*/
|
||||
bool DCE_Mode::set_unsafe(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
|
||||
{
|
||||
switch (m) {
|
||||
case modem_mode::UNDEF:
|
||||
break;
|
||||
case modem_mode::COMMAND_MODE:
|
||||
if (mode == modem_mode::COMMAND_MODE) {
|
||||
return false;
|
||||
{
|
||||
if (mode == modem_mode::COMMAND_MODE) {
|
||||
return false;
|
||||
}
|
||||
if (mode == modem_mode::CMUX_MODE) {
|
||||
netif.stop();
|
||||
netif.wait_until_ppp_exits();
|
||||
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
|
||||
return false;
|
||||
}
|
||||
mode = m;
|
||||
return true;
|
||||
}
|
||||
netif.stop();
|
||||
auto signal = std::make_shared<SignalGroup>();
|
||||
std::weak_ptr<SignalGroup> weak_signal = signal;
|
||||
dte->set_read_cb([weak_signal](uint8_t *data, size_t len) -> bool {
|
||||
if (memchr(data, '\n', len)) {
|
||||
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_DEBUG);
|
||||
const auto pass = std::list<std::string_view>({"NO CARRIER", "DISCONNECTED"});
|
||||
std::string_view response((char *) data, len);
|
||||
for (auto &it : pass)
|
||||
if (response.find(it) != std::string::npos) {
|
||||
if (auto signal = weak_signal.lock())
|
||||
signal->set(1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
netif.wait_until_ppp_exits();
|
||||
if (!signal->wait(1, 2000)) {
|
||||
if (!device->set_mode(modem_mode::COMMAND_MODE)) {
|
||||
mode = modem_mode::UNDEF;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dte->set_read_cb(nullptr);
|
||||
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
|
||||
mode = modem_mode::UNDEF;
|
||||
return false;
|
||||
}
|
||||
mode = m;
|
||||
return true;
|
||||
}
|
||||
netif.stop();
|
||||
if (!device->set_mode(modem_mode::COMMAND_MODE)) {
|
||||
return false;
|
||||
}
|
||||
dte->set_read_cb([&](uint8_t *data, size_t len) -> bool {
|
||||
ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", data, len, ESP_LOG_INFO);
|
||||
return false;
|
||||
});
|
||||
netif.wait_until_ppp_exits();
|
||||
dte->set_read_cb(nullptr);
|
||||
if (!dte->set_mode(modem_mode::COMMAND_MODE)) {
|
||||
return false;
|
||||
}
|
||||
mode = m;
|
||||
return true;
|
||||
break;
|
||||
case modem_mode::DATA_MODE:
|
||||
if (mode == modem_mode::DATA_MODE) {
|
||||
if (mode == modem_mode::DATA_MODE || mode == modem_mode::CMUX_MODE) {
|
||||
return false;
|
||||
}
|
||||
if (!device->setup_data_mode()) {
|
||||
@ -71,7 +117,17 @@ bool DCE_Mode::set(DTE *dte, ModuleIf *device, Netif &netif, modem_mode m)
|
||||
if (!dte->set_mode(modem_mode::CMUX_MODE)) {
|
||||
return false;
|
||||
}
|
||||
mode = modem_mode::COMMAND_MODE;
|
||||
mode = modem_mode::CMUX_MODE;
|
||||
if (!device->setup_data_mode()) {
|
||||
return false;
|
||||
}
|
||||
if (!device->set_mode(modem_mode::DATA_MODE)) {
|
||||
return false;
|
||||
}
|
||||
if (!dte->set_mode(modem_mode::DATA_MODE)) {
|
||||
return false;
|
||||
}
|
||||
netif.start();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include <cstring>
|
||||
#include "esp_log.h"
|
||||
#include "cxx_include/esp_modem_dte.hpp"
|
||||
#include "cxx_include/esp_modem_cmux.hpp"
|
||||
#include "esp_modem_config.h"
|
||||
|
||||
using namespace esp_modem;
|
||||
@ -22,36 +23,34 @@ using namespace esp_modem;
|
||||
static const size_t dte_default_buffer_size = 1000;
|
||||
|
||||
DTE::DTE(const esp_modem_dte_config *config, std::unique_ptr<Terminal> terminal):
|
||||
buffer_size(config->dte_buffer_size), consumed(0),
|
||||
buffer(std::make_unique<uint8_t[]>(buffer_size)),
|
||||
term(std::move(terminal)), command_term(term.get()), other_term(nullptr),
|
||||
buffer(config->dte_buffer_size),
|
||||
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
|
||||
mode(modem_mode::UNDEF) {}
|
||||
|
||||
DTE::DTE(std::unique_ptr<Terminal> terminal):
|
||||
buffer_size(dte_default_buffer_size), consumed(0),
|
||||
buffer(std::make_unique<uint8_t[]>(buffer_size)),
|
||||
term(std::move(terminal)), command_term(term.get()), other_term(nullptr),
|
||||
buffer(dte_default_buffer_size),
|
||||
cmux_term(nullptr), command_term(std::move(terminal)), data_term(command_term),
|
||||
mode(modem_mode::UNDEF) {}
|
||||
|
||||
command_result DTE::command(const std::string &command, got_line_cb got_line, uint32_t time_ms, const char separator)
|
||||
{
|
||||
Scoped<Lock> l(lock);
|
||||
Scoped<Lock> l(internal_lock);
|
||||
command_result res = command_result::TIMEOUT;
|
||||
command_term->set_read_cb([&](uint8_t *data, size_t len) {
|
||||
if (!data) {
|
||||
data = buffer.get();
|
||||
len = command_term->read(data + consumed, buffer_size - consumed);
|
||||
len = command_term->read(data + buffer.consumed, buffer.size - buffer.consumed);
|
||||
} else {
|
||||
consumed = 0; // if the underlying terminal contains data, we cannot fragment
|
||||
buffer.consumed = 0; // if the underlying terminal contains data, we cannot fragment
|
||||
}
|
||||
if (memchr(data + consumed, separator, len)) {
|
||||
res = got_line(data, consumed + len);
|
||||
if (memchr(data + buffer.consumed, separator, len)) {
|
||||
res = got_line(data, buffer.consumed + len);
|
||||
if (res == command_result::OK || res == command_result::FAIL) {
|
||||
signal.set(GOT_LINE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
consumed += len;
|
||||
buffer.consumed += len;
|
||||
return false;
|
||||
});
|
||||
command_term->write((uint8_t *)command.c_str(), command.length());
|
||||
@ -59,7 +58,7 @@ command_result DTE::command(const std::string &command, got_line_cb got_line, ui
|
||||
if (got_lf && res == command_result::TIMEOUT) {
|
||||
throw_if_esp_fail(ESP_ERR_INVALID_STATE);
|
||||
}
|
||||
consumed = 0;
|
||||
buffer.consumed = 0;
|
||||
command_term->set_read_cb(nullptr);
|
||||
return res;
|
||||
}
|
||||
@ -69,39 +68,74 @@ command_result DTE::command(const std::string &cmd, got_line_cb got_line, uint32
|
||||
return command(cmd, got_line, time_ms, '\n');
|
||||
}
|
||||
|
||||
bool DTE::setup_cmux()
|
||||
bool DTE::exit_cmux()
|
||||
{
|
||||
auto original_term = std::move(term);
|
||||
if (original_term == nullptr) {
|
||||
if (!cmux_term->deinit()) {
|
||||
return false;
|
||||
}
|
||||
auto cmux_term = std::make_shared<CMux>(std::move(original_term), std::move(buffer), buffer_size);
|
||||
auto ejected = cmux_term->detach();
|
||||
// return the ejected terminal and buffer back to this DTE
|
||||
command_term = std::move(ejected.first);
|
||||
buffer = std::move(ejected.second);
|
||||
data_term = command_term;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DTE::setup_cmux()
|
||||
{
|
||||
cmux_term = std::make_shared<CMux>(command_term, std::move(buffer));
|
||||
if (cmux_term == nullptr) {
|
||||
return false;
|
||||
}
|
||||
buffer_size = 0;
|
||||
if (!cmux_term->init()) {
|
||||
return false;
|
||||
}
|
||||
term = std::make_unique<CMuxInstance>(cmux_term, 0);
|
||||
if (term == nullptr) {
|
||||
command_term = std::make_unique<CMuxInstance>(cmux_term, 0);
|
||||
if (command_term == nullptr) {
|
||||
return false;
|
||||
}
|
||||
command_term = term.get(); // use command terminal as previously
|
||||
other_term = std::make_unique<CMuxInstance>(cmux_term, 1);
|
||||
data_term = std::make_unique<CMuxInstance>(cmux_term, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DTE::set_mode(modem_mode m)
|
||||
{
|
||||
mode = m;
|
||||
if (m == modem_mode::DATA_MODE) {
|
||||
term->set_read_cb(on_data);
|
||||
if (other_term) { // if we have the other terminal, let's use it for commands
|
||||
command_term = other_term.get();
|
||||
// transitions (COMMAND|UNDEF) -> CMUX
|
||||
if (m == modem_mode::CMUX_MODE) {
|
||||
if (mode == modem_mode::UNDEF || mode == modem_mode::COMMAND_MODE) {
|
||||
if (setup_cmux()) {
|
||||
mode = m;
|
||||
return true;
|
||||
}
|
||||
mode = modem_mode::UNDEF;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// transitions (COMMAND|CMUX|UNDEF) -> DATA
|
||||
if (m == modem_mode::DATA_MODE) {
|
||||
if (mode == modem_mode::CMUX_MODE) {
|
||||
// mode stays the same, but need to swap terminals (as command has been switch)
|
||||
data_term.swap(command_term);
|
||||
} else {
|
||||
mode = m;
|
||||
}
|
||||
// prepare the data terminal's callback to the configured std::function (used by netif)
|
||||
data_term->set_read_cb(on_data);
|
||||
return true;
|
||||
}
|
||||
// transitions (DATA|CMUX|UNDEF) -> COMMAND
|
||||
if (m == modem_mode::COMMAND_MODE) {
|
||||
if (mode == modem_mode::CMUX_MODE) {
|
||||
if (exit_cmux()) {
|
||||
mode = m;
|
||||
return true;
|
||||
}
|
||||
mode = modem_mode::UNDEF;
|
||||
return false;
|
||||
} else {
|
||||
mode = m;
|
||||
return true;
|
||||
}
|
||||
} else if (m == modem_mode::CMUX_MODE) {
|
||||
return setup_cmux();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -109,10 +143,10 @@ bool DTE::set_mode(modem_mode m)
|
||||
void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
|
||||
{
|
||||
on_data = std::move(f);
|
||||
term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||
data_term->set_read_cb([this](uint8_t *data, size_t len) {
|
||||
if (!data) { // if no data available from terminal callback -> need to explicitly read some
|
||||
data = buffer.get();
|
||||
len = term->read(buffer.get(), buffer_size);
|
||||
len = data_term->read(buffer.get(), buffer.size);
|
||||
}
|
||||
if (on_data) {
|
||||
return on_data(data, len);
|
||||
@ -123,14 +157,20 @@ void DTE::set_read_cb(std::function<bool(uint8_t *, size_t)> f)
|
||||
|
||||
int DTE::read(uint8_t **d, size_t len)
|
||||
{
|
||||
auto data_to_read = std::min(len, buffer_size);
|
||||
auto data_to_read = std::min(len, buffer.size);
|
||||
auto data = buffer.get();
|
||||
auto actual_len = term->read(data, data_to_read);
|
||||
auto actual_len = data_term->read(data, data_to_read);
|
||||
*d = data;
|
||||
return actual_len;
|
||||
}
|
||||
|
||||
int DTE::write(uint8_t *data, size_t len)
|
||||
{
|
||||
return term->write(data, len);
|
||||
}
|
||||
return data_term->write(data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented here to keep all headers C++11 compliant
|
||||
*/
|
||||
unique_buffer::unique_buffer(size_t size):
|
||||
data(std::make_unique<uint8_t[]>(size)), size(size), consumed(0) {}
|
||||
|
@ -54,13 +54,16 @@ void Netif::start()
|
||||
signal.set(PPP_STARTED);
|
||||
}
|
||||
|
||||
void Netif::stop() {}
|
||||
void Netif::stop()
|
||||
{
|
||||
ppp_dte->set_read_cb(nullptr);
|
||||
signal.clear(PPP_STARTED);
|
||||
}
|
||||
|
||||
Netif::~Netif() = default;
|
||||
|
||||
void Netif::wait_until_ppp_exits()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} // namespace esp_modem
|
@ -15,13 +15,17 @@ void LoopbackTerm::stop()
|
||||
|
||||
int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
{
|
||||
if (inject_by) { // injection test: ignore what we write, but respond with injected data
|
||||
auto ret = std::async(&LoopbackTerm::batch_read, this);
|
||||
return len;
|
||||
}
|
||||
if (len > 2 && (data[len - 1] == '\r' || data[len - 1] == '+') ) { // Simple AT responder
|
||||
std::string command((char *)data, len);
|
||||
std::string response;
|
||||
if (command == "+++") {
|
||||
response = "NO CARRIER\r\n";
|
||||
} else if (command == "ATE1\r" || command == "ATE0\r") {
|
||||
response = "OK\r\n";
|
||||
response = "OK\r\n ";
|
||||
} else if (command == "ATO\r") {
|
||||
response = "ERROR\r\n";
|
||||
} else if (command.find("ATD") != std::string::npos) {
|
||||
@ -39,7 +43,16 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
} else if (command.find("AT+CPIN?\r") != std::string::npos) {
|
||||
response = pin_ok ? "+CPIN: READY\r\nOK\r\n" : "+CPIN: SIM PIN\r\nOK\r\n";
|
||||
} else if (command.find("AT") != std::string::npos) {
|
||||
response = "OK\r\n";
|
||||
if (command.length() > 4) {
|
||||
response = command;
|
||||
response[0] = 'O';
|
||||
response[1] = 'K';
|
||||
response[2] = '\r';
|
||||
response[3] = '\n';
|
||||
} else {
|
||||
response = "OK\r\n";
|
||||
}
|
||||
|
||||
}
|
||||
if (!response.empty()) {
|
||||
data_len = response.length();
|
||||
@ -51,7 +64,7 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
}
|
||||
if (len > 2 && data[0] == 0xf9) { // Simple CMUX responder
|
||||
// turn the request into a reply -> implements CMUX loopback
|
||||
if (data[2] == 0x3f) { // SABM command
|
||||
if (data[2] == 0x3f || data[2] == 0x53) { // SABM command
|
||||
data[2] = 0x73;
|
||||
} else if (data[2] == 0xef) { // Generic request
|
||||
data[2] = 0xff; // generic reply
|
||||
@ -67,6 +80,8 @@ int LoopbackTerm::write(uint8_t *data, size_t len)
|
||||
int LoopbackTerm::read(uint8_t *data, size_t len)
|
||||
{
|
||||
size_t read_len = std::min(data_len, len);
|
||||
if (inject_by && read_len > inject_by)
|
||||
read_len = inject_by;
|
||||
if (read_len) {
|
||||
if (loopback_data.capacity() < len) {
|
||||
loopback_data.reserve(len);
|
||||
@ -78,8 +93,30 @@ int LoopbackTerm::read(uint8_t *data, size_t len)
|
||||
return read_len;
|
||||
}
|
||||
|
||||
LoopbackTerm::LoopbackTerm(bool is_bg96): loopback_data(), data_len(0), pin_ok(false), is_bg96(is_bg96) {}
|
||||
LoopbackTerm::LoopbackTerm(bool is_bg96): loopback_data(), data_len(0), pin_ok(false), is_bg96(is_bg96), inject_by(0) {}
|
||||
|
||||
LoopbackTerm::LoopbackTerm(): loopback_data(), data_len(0), pin_ok(false), is_bg96(false) {}
|
||||
LoopbackTerm::LoopbackTerm(): loopback_data(), data_len(0), pin_ok(false), is_bg96(false), inject_by(0) {}
|
||||
|
||||
int LoopbackTerm::inject(uint8_t *data, size_t len, size_t injected_by)
|
||||
{
|
||||
if (data == nullptr) {
|
||||
inject_by = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
loopback_data.resize(len);
|
||||
memcpy(&loopback_data[0], data, len);
|
||||
data_len = len;
|
||||
inject_by = injected_by;
|
||||
return len;
|
||||
}
|
||||
|
||||
void LoopbackTerm::batch_read()
|
||||
{
|
||||
while (data_len > 0) {
|
||||
on_read(nullptr, std::min(inject_by, data_len));
|
||||
Task::Delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
LoopbackTerm::~LoopbackTerm() = default;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user