Compare commits

..

77 Commits

Author SHA1 Message Date
055f051f53 ASIO: Initial version based on IDF 5.0 with history 2022-06-24 15:33:10 +04:00
ac7bf465d2 asio: Disable concepts support
Fix example for compatibility with C++20

* Original commit: espressif/esp-idf@9dba0476a0
* Original commit: espressif/esp-idf@157247f98f
2022-06-22 17:58:30 +04:00
2cf7518114 ci: partially enable example build for esp32c2
* Original commit: espressif/esp-idf@f7be540222
2022-06-22 14:18:18 +04:00
057a5d2db8 lwip: Update socket API to include port-version of sockets/netdb
Added socket extention to the lwip hooks for implementing non-vanilla
{get/set}sockopts()


* Original commit: espressif/esp-idf@53c009e626
2022-06-22 14:18:18 +04:00
63bff632df esp_netif/lwip: Implement basic support for vanilla-lwip (2.1.3-REL)
* Reference lwip-2.1.3-REL vanilla lwip version
* Use inherent NETIF callbacks instead of dhcp/ipv6/autoip


* Original commit: espressif/esp-idf@5b471a1848
2022-06-22 14:18:18 +04:00
938ddf16b9 tools: Increase the minimal supported CMake version to 3.16
This updates the minimal supported version of CMake to 3.16, which in turn enables us to use more CMake features and have a cleaner build system.
This is the version that provides most new features and also the one we use in our latest docker image for CI.


* Original commit: espressif/esp-idf@facab8c5a7
2022-06-22 14:18:18 +04:00
0c84c9750c docs: update redirected links
* Original commit: espressif/esp-idf@030cb77597
2022-06-22 14:18:18 +04:00
952e5df477 [EXAMPLES/ASIO] Renames Kconfig parameter and clarify
On ip::tcp::resolver ASIO uses service to identify the type of
connection and get the port number. LWIP can't resolve this way.


* Original commit: espressif/esp-idf@232b0eda5f
2022-06-22 14:18:18 +04:00
e3c4391246 mbedtls: Remove certs.c and certs.h from port directory
* Original commit: espressif/esp-idf@f31d8dd295
2022-06-22 14:18:18 +04:00
98138189c9 mbedtls-3 update:
1) Fix build issue in mbedtls
2) skip the public headers check in IDF
3)Update Kconfig Macros
4)Remove deprecated config options
5) Update the sha API according to new nomenclature
6) Update mbedtls_rsa_init usage
7) Include mbedtls/build_info.h instead of mbedtls/config.h
8) Dont include check_config.h
9) Add additional error message in esp_blufi_api.h


* Original commit: espressif/esp-idf@45122533e0
2022-06-22 14:18:18 +04:00
30dae8f7ed EXAMPLES/ASIO: Adds a SOCKS4 example
Creates an example on how to connect using Socks4 based proxy.


* Original commit: espressif/esp-idf@f7b842bbc7
2022-06-22 14:18:18 +04:00
b76d3fbff9 EXAMPLE/ASIO Async HTTP request
Introduces a new example on ASIO to ilustrates on how to compose
async operation to build network related protocols.


* Original commit: espressif/esp-idf@2433816b14
2022-06-22 14:18:18 +04:00
f605fdd632 asio: Use internal ssl context and engine impl
Implement asio-ssl layer with three classes in asio::ssl::mbedtls:
* context -- replaces SSL_CTX, used mainly as a container to options,
certs, keys
* engine -- replaces SSL, implements the actual mbedtls operations
* bio -- implements openssl BIO specifically tailered to mbedtls and
its asio usage

Further updates:
* asio: Used shared_ptr<> for bio pairs
* asio: Add error checks to mbedtls-bio
* asio: Address potential ssl-context ownership issue
* asio: Address potential bio-engine ownership issue


* Original commit: espressif/esp-idf@d823106aa6
2022-06-22 14:18:18 +04:00
abbc8d9c5a Build & config: Remove leftover files from the unsupported "make" build system
* Original commit: espressif/esp-idf@766aa57084
2022-06-22 14:18:18 +04:00
a0297743dd openssl: Add deprecation warning to ssl.h
OpenSSL component will be discontinued in ESP-IDF v5.x. The only official TLS API will be esp-tls
Add docs warning about deprecation in v5.x

Removed all examples demonstrating usage of openssl in IDF


* Original commit: espressif/esp-idf@cfc001870c
2022-06-22 14:18:18 +04:00
3bcc46276a example_tests: Deletes usage esp32c3 ECO0 in CI (by default ECO3)
* Original commit: espressif/esp-idf@709abee65c
2022-06-22 14:18:18 +04:00
f0d0698582 docs: update asio docs with new example paths after refactor.
* Original commit: espressif/esp-idf@37d549916b
2022-06-22 14:18:18 +04:00
91262baede examples: Asio tests consolidation to use loopback interface
Closes IDF-3072


* Original commit: espressif/esp-idf@a06357ab62
2022-06-22 14:18:18 +04:00
cb2375827b CI: fix connection failures in asio example tests
* Original commit: espressif/esp-idf@b3b71bc8ab
2022-06-22 14:18:17 +04:00
cc0f2b3cf0 asio coap: If LWIP IPV6 is disabled, automatically don't build asio & coap
- Removes need to manually exclude these components as shown at
  https://github.com/espressif/esp-idf/issues/3781#issuecomment-825742378

- Hide the config for these components if IPV6 is disabled

- The components are still included in the build, but with no source
  files


* Original commit: espressif/esp-idf@e305f29382
2022-06-22 14:18:17 +04:00
47d57a5b14 asio: update copyright notice
* Original commit: espressif/esp-idf@2d0895e9a9
2022-06-22 14:18:17 +04:00
278b7bef3c partition_table: Add a "single factory app (large)" option for 1.5MB app size
Needed to build some examples in their default configurations (especially
ESP32-C3). Wasn't noticed until the CI checks for partition size were added.


* Original commit: espressif/esp-idf@920edd4e73
2022-06-22 14:18:17 +04:00
a165452ca1 Split example_tests with Example_WIFI tag group into Example_OTA and Example_Protocols
* Original commit: espressif/esp-idf@0a395134d4
2022-06-22 14:18:17 +04:00
55f95fb790 example_tests: CI uses ECO0 for esp32c3 tests
* Original commit: espressif/esp-idf@ec4de4fe5c
2022-06-22 14:18:17 +04:00
9b76388163 ci: run Example_GENERIC for C3
Add support for running example_GENERIC tests for C3 on label.

Fix examples that fail.


* Original commit: espressif/esp-idf@c85d949c1f
2022-06-22 14:18:17 +04:00
0d1d73eb35 style: format python files with isort and double-quote-string-fixer
* Original commit: espressif/esp-idf@0146f258d7
2022-06-22 14:18:17 +04:00
88d3eda156 test: remove fake binary size check in example test:
the binary size check in example test was removed long time ago. Now we
have updated ttfw_idf to raise exception when performance standard is
not found. These fake performance check will be regarded as error.
Remove them now.


* Original commit: espressif/esp-idf@a908174c06
2022-06-22 14:18:17 +04:00
622a360e97 Whitespace: Automated whitespace fixes (large commit)
Apply the pre-commit hook whitespace fixes to all files in the repo.

(Line endings, blank lines at end of file, trailing whitespace)


* Original commit: espressif/esp-idf@66fb5a29bb
2022-06-22 14:18:17 +04:00
4358c3ceab cmake: Apply cmakelint fixes
* Original commit: espressif/esp-idf@e82eac4354
2022-06-22 14:18:17 +04:00
f00c3be139 asio: make the example code conform to Espressif C++ standards
* Original commit: espressif/esp-idf@b2150f86a5
2022-06-22 14:18:17 +04:00
c05558ba28 asio: option to use wolfSSL as TLS stack for ASIO
Plus other minor update, make openssl aware of current modes (SSL_set_mode)
Update coding style in examples and tests, including copyright notices


* Original commit: espressif/esp-idf@1c8171c3e8
2022-06-22 14:18:17 +04:00
c0c1a65598 examples: asio ssl example demonstrating both server and client
By default it uses simple client connecting to https address. It is
possible to configure both server and client. As for example test the
configuration of both server and client connecting to each other on


* Original commit: espressif/esp-idf@213bbe51fc
2022-06-22 14:18:17 +04:00
dab12309e2 asio: Basic SSL/TLS support in asio port for ESP platform
This port employs IDF port of OpenSSL for most common features, others
are discouraged or not supported. The port also introduces several stubs
for OpenSSL functions which ASIO needs to get compiled and linked.

Upstream ASIO supports WolfSSL as SSL/TLS stack, as well, which is
another option for SSL support in ASIO on ESP platform.


* Original commit: espressif/esp-idf@9459c0dd43
2022-06-22 14:18:17 +04:00
789670e8c5 Add multi-target support for performance tests
* Original commit: espressif/esp-idf@15884eccf2
2022-06-22 14:18:17 +04:00
131613c5cc doc: fix broken example links
* Original commit: espressif/esp-idf@f2cb6bd4b6
2022-06-22 14:18:17 +04:00
919091766d asio: updated ASIO port to use latest asio and esp-idf features
closes https://github.com/espressif/esp-idf/issues/4296


* Original commit: espressif/esp-idf@13d603e486
2022-06-22 14:18:17 +04:00
4e4ab0908d test: update example and unit tests with new import roles:
tiny_test_fw is a python package now. import it using normal way.


* Original commit: espressif/esp-idf@c906e2afee
2022-06-22 14:18:17 +04:00
54e3a85983 esp_netif and examples: using wifi driver handle, update examples and tests to pass the CI
* Original commit: espressif/esp-idf@3a19bf055d
2022-06-22 14:18:17 +04:00
98dfb691b6 asio: fix asio test code to start the test after ip address received from common example code
Previously set to wait until IP address got from tcpip_adapter, but since common example connect code blocks until both IP4 and IPv6 address received it could happen that test code might have started connection to the ASIO counter-part while ESP32 still waiting for IPv6 address


* Original commit: espressif/esp-idf@208feef3c9
2022-06-22 14:18:17 +04:00
ff7d214f9c ASIO: fixed undefined ref to atomic functions and enabled examples for CI (esp32s2beta)
Implemented the atomic functions needed to compile and link the asio examples on esp32s2beta.


* Original commit: espressif/esp-idf@845003a1c3
2022-06-22 14:18:17 +04:00
ee6dbbfeaa ci: limit example test to ESP32s
* Original commit: espressif/esp-idf@63329b169b
2022-06-22 14:18:17 +04:00
a14331ad01 tools: Mass fixing of empty prototypes (for -Wstrict-prototypes)
* Original commit: espressif/esp-idf@afbaf74007
2022-06-22 14:18:17 +04:00
f3754a3683 examples: change default build instructions in docs to CMake
* Original commit: espressif/esp-idf@e7dba7d7bc
2022-06-22 14:18:17 +04:00
c1176ccdcc build system: Use CMake-based build system as default when describing commands
* Original commit: espressif/esp-idf@47bbb107a8
2022-06-22 14:18:17 +04:00
df51e60ac0 examples: use new component registration api
* Original commit: espressif/esp-idf@6771eead80
2022-06-22 14:18:17 +04:00
9e83b1eb0e components: use new component registration api
* Original commit: espressif/esp-idf@9eccd7c082
2022-06-22 14:18:17 +04:00
f82cbfde7b ci: support to build esp32s2beta simple examples
* Original commit: espressif/esp-idf@25ab8380c8
2022-06-22 14:18:17 +04:00
96d13293ab Rename Kconfig options (components/esp32)
* Original commit: espressif/esp-idf@0ae53691ba
2022-06-22 14:18:17 +04:00
3b49d1f559 Rename Kconfig options (root)
* Original commit: espressif/esp-idf@c5000c83d2
2022-06-22 14:18:17 +04:00
3e7591e92a examples/protocols/asio: use common network component
* Original commit: espressif/esp-idf@aa4a7804ec
2022-06-22 14:18:17 +04:00
de830e51d4 Correct Kconfigs according to the coding style
* Original commit: espressif/esp-idf@37126d3451
2022-06-22 14:18:17 +04:00
21c0878f0e examples: Fix Python coding style
* Original commit: espressif/esp-idf@57c54f96f1
2022-06-22 14:18:17 +04:00
a96c890f97 Fix Python 3 compatibility issues
* Original commit: espressif/esp-idf@9daf51e6be
2022-06-22 14:18:17 +04:00
86d107635b asio: examples renamed to have consistent binary names when build in make and CMake
* Original commit: espressif/esp-idf@9784df1c3a
2022-06-22 14:18:17 +04:00
57672d5add cmake: make main a component again
* Original commit: espressif/esp-idf@d9939cedd9
2022-06-22 14:18:17 +04:00
e8ea8786d2 cmake: Add missing example CMakeLists.txt files, CI check all examples have both
* Original commit: espressif/esp-idf@5689e446dc
2022-06-22 14:18:17 +04:00
142200c7ca Merge branch 'master' into feature/cmake
* Original commit: espressif/esp-idf@ff2404a272
2022-06-22 14:18:17 +04:00
6c47cfe30a asio: added socket timeout for example tests
* Original commit: espressif/esp-idf@c0186858ad
2022-06-22 14:18:17 +04:00
0ae8c3ca33 asio example tests: Increase timeout for DHCP lease to 30s
Covers time to connect to WiFi and negotiate lease,
may be more than 5-10s on some busy APs


* Original commit: espressif/esp-idf@8b35d8ef25
2022-06-22 14:18:17 +04:00
5472d5c52f asio: initial idf port of asio library without ssl
* Original commit: espressif/esp-idf@1ef13c524c
2022-06-22 14:18:17 +04:00
51a50db0fd Merge pull request #61 from gabsuren/feature/common_component_esp_dependency
protocol_examples_common: Updated to explicitly use esp-eth dependency if needed
2022-06-21 18:47:35 +02:00
e1660a1806 protocol_examples_common: Updated to explicitely use esp-eth dependency if needed
* Original commit: espressif/esp-idf@5e19b9c951
2022-06-21 20:39:46 +04:00
84f18bddfb Merge pull request #58 from gabsuren/feature/mdns_esp_dependency
mdns: Updated mDNS to explicitly use esp-eth dependency if needed
2022-06-21 18:09:46 +02:00
609594a849 Merge pull request #42 from david-cermak/feature/modem_cmux_ext
esp_modem: CMUX mode improvements
2022-06-21 18:08:34 +02:00
66e6d4cbf8 fix(esp_modem): Implement movable unique_buffer to bundle data, size, ptr
Also improves and fixes tests
2022-06-18 12:19:07 +02:00
4868689111 mdns: Updated mDNS to explicitely use esp-eth dependency if needed
* Original commit: espressif/esp-idf@5e19b9c951
2022-06-17 13:24:22 +04:00
f3ff98bb82 fix(exp_modem): DTE should own both command and data terminal
reworks to clear shared_ptr<> of both command and data terminals
and having DTE own it. When we swith to CMUX mode the ownership is transfered to CMux terminal
2022-06-11 21:33:51 +02:00
58a0b57e12 fix(esp_modem): Update CMUX example to use CMUX mode automatically 2022-06-11 21:29:25 +02:00
3fd4391c38 feat(esp_modem): Add CMUX mode to C-API
Closes https://github.com/espressif/esp-protocols/issues/41
2022-06-11 15:07:39 +02:00
a16aab6979 feat(esp_modem): Add support to CMUX exit
Closes https://github.com/espressif/esp-protocols/issues/37
2022-06-11 15:07:39 +02:00
1e0aefd72a fix(esp-modem): Improve PPP exit sequence
* Implement retry mechanism if escape PPP mode failed
* Stop networking and checking for NO-CARRIER manually on PPP exit

Closes https://github.com/espressif/esp-protocols/issues/47
2022-06-11 15:07:39 +02:00
ebf122bd36 Merge pull request #53 from david-cermak/bugfix/cmux_2byte_len
esp_modem: Support 2 byte size packets
2022-06-10 15:26:31 +02:00
5addf9e885 feat(esp_modem): Bumped version number to 0.1.17 2022-06-10 14:56:12 +02:00
6dbfc69627 Merge pull request #49 from trombik/feat-ca-bundle
feat(websocket): allow users to attach CA bundle (IDFGH-7520)
2022-06-06 15:11:51 +02:00
128c0a2d87 fix(esp_modem): Support 2 byte size packets
Closes https://github.com/espressif/esp-protocols/issues/46
2022-06-06 15:05:38 +02:00
1f91d248f5 Merge pull request #51 from gabsuren/bugfix/mix_fixes
Minor fixes here and there
2022-06-03 15:49:26 +02:00
d56b5d90ea feat(websocket): allow users to attach CA bundle
with esp_transport_ssl_crt_bundle_attach(), it is not necessary to
include a specific certificate. other protocol clients, such as
esp_http_client, do the same.

fixes #48
2022-06-02 12:29:22 +07:00
107 changed files with 4576 additions and 98 deletions

View File

@ -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

View File

@ -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
View 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

View File

@ -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
View File

@ -0,0 +1,3 @@
[submodule "components/asio/asio"]
path = components/asio/asio
url = https://github.com/espressif/asio

View File

@ -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)

View File

@ -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
)

View File

@ -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()

View 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
View 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
View 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

Submodule components/asio/asio added at 58384fb6af

73
components/asio/docs/Doxyfile Executable file
View 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.

View 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']

View 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'

View 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

View 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

View 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'

View File

@ -0,0 +1 @@
.. include:: ../../../en/api-reference/protocols/asio.rst

View 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)

View 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.

View 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()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "asio_chat.cpp"
INCLUDE_DIRS ".")

View 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

View 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());
}

View 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

View 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

View 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

View 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"

View File

@ -0,0 +1,2 @@
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View 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)

View 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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "async_http_request.cpp"
INCLUDE_DIRS ".")

View File

@ -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());
}

View 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)

View 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).

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "socks4.cpp"
INCLUDE_DIRS ".")

View 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

View 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());
}

View 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

View File

@ -0,0 +1,3 @@
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_RTTI=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0

View 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)

View 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.

View 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()

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "asio_ssl_main.cpp"
INCLUDE_DIRS "."
EMBED_TXTFILES ca.crt server.key srv.crt)

View File

@ -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

View File

@ -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();
}
}

View 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-----

View 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-----

View 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-----

View File

@ -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,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1400000,

View 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

View File

@ -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

View 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)

View 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.

View File

@ -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()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "echo_server.cpp"
INCLUDE_DIRS ".")

View File

@ -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

View 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();
}

View File

@ -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

View 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)

View 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.

View File

@ -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()

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "udp_echo_server.cpp"
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -0,0 +1,5 @@
version: "1.0.1"
description: ASIO
dependencies:
idf:
version: ">=5.0"

View 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_

View 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_

View 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;

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,6 @@
//
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once

View File

@ -0,0 +1,6 @@
//
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once

View File

@ -0,0 +1,6 @@
//
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once

View File

@ -0,0 +1,6 @@
//
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once

View 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"

View File

@ -0,0 +1,6 @@
//
// SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
//
// SPDX-License-Identifier: BSL-1.0
//
#pragma once

View 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

View 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

View 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

View File

@ -10,6 +10,7 @@
#pragma once
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
#include <esp_console.h>

View File

@ -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;
}

View File

@ -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:

View File

@ -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{};
};
}

View File

@ -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;
};

View File

@ -40,6 +40,7 @@ public:
modem_mode get();
private:
bool set_unsafe(DTE *dte, ModuleIf *module, Netif &netif, modem_mode m);
modem_mode mode;
};

View File

@ -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;
}

View File

@ -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 */
};
/**

View File

@ -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;

View File

@ -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;
/**

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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;

View File

@ -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) {}

View File

@ -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

View File

@ -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