ASIO: Initial version based on IDF 5.0 with history

This commit is contained in:
gabsuren
2022-06-22 14:49:37 +04:00
parent ac7bf465d2
commit 055f051f53
64 changed files with 250 additions and 15 deletions

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)

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"