chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# 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.22)
|
||||
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(https_server)
|
||||
@@ -0,0 +1,75 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# HTTPS server
|
||||
|
||||
This example creates an HTTPS server with SSL/TLS support using **ESP-TLS** that serves a simple HTML page when you visit its root URL.
|
||||
|
||||
For more information, refer to the [esp_https_server component documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_https_server.html).
|
||||
|
||||
## How to use example
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
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.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Certificates
|
||||
|
||||
You will need to approve a security exception in your browser. This is because of a self signed
|
||||
certificate; this will be always the case, unless you preload the CA root into your browser/system
|
||||
as trusted.
|
||||
|
||||
You can generate a new certificate using the OpenSSL command line tool:
|
||||
|
||||
```
|
||||
openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -x509 -days 3650 -out servercert.pem -subj "/CN=ESP32 HTTPS server example" -addext "keyUsage=critical,digitalSignature,keyEncipherment" -addext "subjectAltName=IP:<server_ip_address>"
|
||||
```
|
||||
|
||||
Replace `<server_ip_address>` with your ESP32's actual IP address (e.g., `192.168.20.178`). The Subject Alternative Name (SAN) extension is required for modern browsers to validate the certificate when connecting via IP address.
|
||||
|
||||
**Important:** Use a static IP address for your ESP32. If the device uses a dynamic IP address, the IP may change, and you will need to regenerate the certificate with the new IP address each time it changes.
|
||||
|
||||
**Note:** The pre-generated certificates in the `main/certs` directory do not include the SAN extension and will not work with modern browsers. You must generate new certificates with the SAN field containing your server's IP address for browser compatibility.
|
||||
|
||||
Expiry time and metadata fields can be adjusted in the invocation.
|
||||
|
||||
Please see the openssl man pages (man openssl-req) for more details.
|
||||
|
||||
It is **strongly recommended** to not reuse the example certificate in your application;
|
||||
it is included only for demonstration.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (8596) example: Starting server
|
||||
I (8596) esp_https_server: Starting server
|
||||
I (8596) esp_https_server: Server listening on port 443
|
||||
I (8596) example: Registering URI handlers
|
||||
I (8606) esp_netif_handlers: example_connect: sta ip: 192.168.194.219, mask: 255.255.255.0, gw: 192.168.194.27
|
||||
I (8616) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.194.219
|
||||
I (9596) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:266f:28ff:fe80:2c74, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (9596) example_connect: Connected to example_connect: sta
|
||||
W (9606) wifi:<ba-add>idx:0 (ifx:0, ee:6d:19:60:f6:0e), tid:0, ssn:2, winSize:64
|
||||
I (9616) example_connect: - IPv4 address: 192.168.194.219
|
||||
I (9616) example_connect: - IPv6 address: fe80:0000:0000:0000:266f:28ff:fe80:2c74, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
W (14426) wifi:<ba-add>idx:1 (ifx:0, ee:6d:19:60:f6:0e), tid:4, ssn:0, winSize:64
|
||||
I (84896) esp_https_server: performing session handshake
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
set(requires esp_https_server nvs_flash)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND requires esp_stubs protocol_examples_common)
|
||||
else()
|
||||
list(APPEND requires esp_wifi esp_eth)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES ${requires}
|
||||
EMBED_TXTFILES "certs/servercert.pem"
|
||||
"certs/prvtkey.pem"
|
||||
"certs/cacert.pem"
|
||||
"certs/cakey.pem")
|
||||
@@ -0,0 +1,45 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ENABLE_HTTPS_USER_CALLBACK
|
||||
bool "Enable user callback with HTTPS Server"
|
||||
help
|
||||
Enable user callback for esp_https_server which can be used to get SSL context (connection information)
|
||||
E.g. Certificate of the connected client
|
||||
|
||||
choice EXAMPLE_ENABLE_HTTPS_SERVER_TLS_VERSION
|
||||
prompt "Choose TLS version"
|
||||
default EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
|
||||
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
|
||||
bool "TLS 1.2"
|
||||
select MBEDTLS_SSL_PROTO_TLS1_2
|
||||
help
|
||||
Enable HTTPS server TLS 1.2
|
||||
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
|
||||
bool "TLS 1.3"
|
||||
select MBEDTLS_SSL_PROTO_TLS1_3
|
||||
help
|
||||
Enable HTTPS server TLS 1.3
|
||||
config EXAMPLE_ENABLE_HTTPS_SERVER_TLS_ANY
|
||||
bool "TLS 1.3 and 1.2"
|
||||
select MBEDTLS_SSL_PROTO_TLS1_3
|
||||
select MBEDTLS_SSL_PROTO_TLS1_2
|
||||
help
|
||||
Enable HTTPS server TLS 1.3 and 1.2
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
|
||||
bool "Enable HTTPS server custom ciphersuites"
|
||||
default n
|
||||
help
|
||||
Enable HTTPS server custom ciphersuites
|
||||
config EXAMPLE_ENABLE_SKIP_CLIENT_CERT
|
||||
bool "Skip client certificate (WARNING: ONLY FOR TESTING PURPOSE, READ HELP)"
|
||||
default n
|
||||
select ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL
|
||||
help
|
||||
Allow clients to connect without providing a client certificate.
|
||||
This is useful for testing purposes. When enabled, the server request
|
||||
client certificates but does not require them for the connection to be established.
|
||||
If a client certificate is provided, it will be verified.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,120 @@
|
||||
# Certificate Generation Guide
|
||||
|
||||
This directory contains certificates for the HTTPS server example. This guide explains how to generate new server and client certificates signed by the existing CA certificate.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- OpenSSL installed on your system
|
||||
- Existing CA certificate (`cacert.pem`) and CA private key (`cakey.pem`)
|
||||
- Configuration files for certificate extensions (`server_cert.conf` and `client_cert.conf`)
|
||||
|
||||
## Generating Server Certificate
|
||||
|
||||
Follow these steps to create a new server certificate signed by the CA:
|
||||
|
||||
### 1. Generate Server Private Key
|
||||
|
||||
```bash
|
||||
openssl genpkey -algorithm RSA -out new_server.key -pkeyopt rsa_keygen_bits:2048
|
||||
```
|
||||
|
||||
This creates a 2048-bit RSA private key for the server.
|
||||
|
||||
### 2. Create Certificate Signing Request (CSR)
|
||||
|
||||
```bash
|
||||
openssl req -new -key new_server.key -out new_server.csr -config server_cert.conf
|
||||
```
|
||||
|
||||
This generates a CSR using the server's private key and the configuration specified in `server_cert.conf`.
|
||||
|
||||
### 3. Sign the Server Certificate with CA
|
||||
|
||||
```bash
|
||||
openssl x509 -req -in new_server.csr -CA cacert.pem -CAkey cakey.pem -CAcreateserial -out new_server.pem -days 3650 -extensions v3_req -extfile server_cert.conf
|
||||
```
|
||||
|
||||
This creates the server certificate (`new_server.pem`) valid for 10 years (3650 days), signed by the CA certificate.
|
||||
|
||||
## Generating Client Certificate
|
||||
|
||||
Follow these steps to create a new client certificate signed by the CA:
|
||||
|
||||
### 4. Generate Client Private Key
|
||||
|
||||
```bash
|
||||
openssl genpkey -algorithm RSA -out new_client.key -pkeyopt rsa_keygen_bits:2048
|
||||
```
|
||||
|
||||
This creates a 2048-bit RSA private key for the client.
|
||||
|
||||
### 5. Create Certificate Signing Request (CSR)
|
||||
|
||||
```bash
|
||||
openssl req -new -key new_client.key -out new_client.csr -config client_cert.conf
|
||||
```
|
||||
|
||||
This generates a CSR using the client's private key and the configuration specified in `client_cert.conf`.
|
||||
|
||||
### 6. Sign the Client Certificate with CA
|
||||
|
||||
```bash
|
||||
openssl x509 -req -in new_client.csr -CA cacert.pem -CAkey cakey.pem -CAcreateserial -out new_client.pem -days 3650 -extensions v3_req -extfile client_cert.conf
|
||||
```
|
||||
|
||||
This creates the client certificate (`new_client.pem`) valid for 10 years (3650 days), signed by the CA certificate.
|
||||
|
||||
## Installing the Certificates
|
||||
|
||||
### 7. Copy Certificates to Expected Locations
|
||||
|
||||
```bash
|
||||
cp new_server.pem servercert.pem && \
|
||||
cp new_server.key prvtkey.pem && \
|
||||
cp new_client.pem client_cert.pem && \
|
||||
cp new_client.key client_key.pem
|
||||
```
|
||||
|
||||
This copies the newly generated certificates and keys to the filenames expected by the example application.
|
||||
|
||||
## File Naming Convention
|
||||
|
||||
The example application expects the following files:
|
||||
|
||||
- `servercert.pem` - Server certificate
|
||||
- `prvtkey.pem` - Server private key
|
||||
- `client_cert.pem` - Client certificate
|
||||
- `client_key.pem` - Client private key
|
||||
- `cacert.pem` - CA certificate (for verification)
|
||||
|
||||
## Security Notes
|
||||
|
||||
⚠️ **Important Security Considerations:**
|
||||
|
||||
- The private keys (`prvtkey.pem`, `client_key.pem`, `cakey.pem`) should be kept secure. As these are for demonstration purposes, they are included here, but in a production environment, ensure they are stored securely and access is restricted.
|
||||
- The certificates in this example directory are for **demonstration purposes only**
|
||||
- For production use, generate new certificates with appropriate security parameters
|
||||
- Consider using shorter validity periods for production certificates
|
||||
- Store private keys with restricted file permissions (e.g., `chmod 600`)
|
||||
|
||||
## Verifying Generated Certificates
|
||||
|
||||
You can verify the generated certificates using:
|
||||
|
||||
```bash
|
||||
# Verify server certificate
|
||||
openssl x509 -in servercert.pem -text -noout
|
||||
|
||||
# Verify client certificate
|
||||
openssl x509 -in client_cert.pem -text -noout
|
||||
|
||||
# Verify certificate chain
|
||||
openssl verify -CAfile cacert.pem servercert.pem
|
||||
openssl verify -CAfile cacert.pem client_cert.pem
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If certificate verification fails, ensure the CA certificate and key are valid and match
|
||||
- Check that the configuration files (`server_cert.conf`, `client_cert.conf`) contain appropriate Subject Alternative Names (SANs) and extensions
|
||||
- Ensure OpenSSL version is up to date for best compatibility
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDOzCCAiOgAwIBAgIUG/S51QF4EeUkdaqg54oogqIKBZkwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMjUw
|
||||
NDAyMDcwMzI2WhcNMzUwMzMxMDcwMzI2WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
|
||||
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
|
||||
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
|
||||
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
|
||||
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
|
||||
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
|
||||
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNjMGEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
|
||||
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQBP
|
||||
AgAagM33DqsDi+UArUxEoqmov1rH0PHXnd/a6Ct/IvNzr0qUH8hW4Lv0tWHfOJY8
|
||||
pCf7bkejxXlhP/QHb6M+sobN9tN/WupEaeqNg4pCWi+6Caj2uFW9vkQQf2j50lMg
|
||||
R0oxnd6SMEQArzy3f3yYRp8rliPERY6F2Rtb9HJNh53K51FE60xONPLZ/1dtSgDB
|
||||
KcJseZfhg6oAUSLjFCYJEn5xa7CsIuQ8Jx2xMo4IkU44BJ8TJS4zw/hP1/vVjjvS
|
||||
uU2Z0ZOUCQ78/3eMnsFfLMtDUYqXPyhNogm51GeHOR6dk+ICQ+c5gCDkJUnOTqzg
|
||||
G2JUmXAXxJoUZDfalijl
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
|
||||
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
|
||||
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
|
||||
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
|
||||
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
|
||||
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
|
||||
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
|
||||
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
|
||||
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
|
||||
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
|
||||
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
|
||||
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
|
||||
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
|
||||
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
|
||||
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
|
||||
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
|
||||
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
|
||||
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
|
||||
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
|
||||
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
|
||||
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
|
||||
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
|
||||
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
|
||||
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
|
||||
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
|
||||
v/t+MeGJP/0Zw8v/X2CFll96
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,16 @@
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = SG
|
||||
ST = SG
|
||||
L = SG
|
||||
O = ESP32 Client
|
||||
CN = ESP32 Test Client
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDfDCCAmSgAwIBAgIUeWALux8MJqS983GSSUHotYp9OxgwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMjUx
|
||||
MDEwMDIzOTIxWhcNMzUxMDA4MDIzOTIxWjBaMQswCQYDVQQGEwJTRzELMAkGA1UE
|
||||
CAwCU0cxCzAJBgNVBAcMAlNHMRUwEwYDVQQKDAxFU1AzMiBDbGllbnQxGjAYBgNV
|
||||
BAMMEUVTUDMyIFRlc3QgQ2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAsWm6XLeoa2dVtUn+hEZMULUSS0RqcE9kPHBQCwRNSlDs6eUSy4Y45SO0
|
||||
P5aqDLd7vMcDg41YPDbXp6JhvKov2Q7pAVbwhrLzn+M6O7XS+7i8J9PzgjBTKMhC
|
||||
rLkGBYbyW04STIbgHmQ0G98y6deyXnkqg/d0OzNaB4XgOiRbuxw1IaPX2loTS5aV
|
||||
hnuo/5tdcyiNMAjio/P1GrkE1OPSUwbdxROjmZW5peFkD4sogW/XH0oMYpMKwyWZ
|
||||
cHiuj5E4IxeazqaJqzjbXIj7+hTywE2kXZDVK1o02JbkDRhig5NkXv38DyNu8R8W
|
||||
dvh3GBczZS7lIn+2fjr3soILMOJ/NwIDAQABo28wbTAJBgNVHRMEAjAAMAsGA1Ud
|
||||
DwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUubkj5sTevwdf
|
||||
SMd2J5WyKkrqtfIwHwYDVR0jBBgwFoAUyeZ0ko4R6lescj+K8zqZWomABfwwDQYJ
|
||||
KoZIhvcNAQELBQADggEBAJmjtzv5aMkNKw8+EwDrdOHJsw6czOtPeVLTmIy02+Pv
|
||||
4rvn+4VDCzogM7oBbFzFFGwUmBXqfdmP4wteqtfdCwHboiCzZySPw0O+OMbhQmEF
|
||||
mCE+k+sCUEWrb3tiraVWh1aipoqOqE0WNcyZ1ZwY1OCMaVYsfFXw3lcND/7VXjp6
|
||||
13WSO46avn3WvesTS/GhLg8yTJiQ8iPXMEEKhf7BTAn461ZsGR3hnz+DPtWTTOaI
|
||||
9fCdjvvYsL4+k3+Y10LWUSsmB6kjyzCmx7/I4NeUEGmMAD4PD5dkkksw+TZBobsb
|
||||
APf44t+dQGYc0dhfym7b+qeW8MTvmx40ZYL52HG7XCU=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxabpct6hrZ1W1
|
||||
Sf6ERkxQtRJLRGpwT2Q8cFALBE1KUOzp5RLLhjjlI7Q/lqoMt3u8xwODjVg8Nten
|
||||
omG8qi/ZDukBVvCGsvOf4zo7tdL7uLwn0/OCMFMoyEKsuQYFhvJbThJMhuAeZDQb
|
||||
3zLp17JeeSqD93Q7M1oHheA6JFu7HDUho9faWhNLlpWGe6j/m11zKI0wCOKj8/Ua
|
||||
uQTU49JTBt3FE6OZlbml4WQPiyiBb9cfSgxikwrDJZlweK6PkTgjF5rOpomrONtc
|
||||
iPv6FPLATaRdkNUrWjTYluQNGGKDk2Re/fwPI27xHxZ2+HcYFzNlLuUif7Z+Ovey
|
||||
ggsw4n83AgMBAAECggEAMLZZg7yvwzG/0EOtXQ9aQ+y7xavW19iMqqWh7Kx1NlsK
|
||||
+du6ceR8Obo4cx9AuLYmhPpV5iiImhvq0a3dzSojciNMaeA/sZRwHS4MXrm5YQFj
|
||||
tEHXgh8XrkJyQC+bTigz4ksI7jc4UU/tGNwLhDaD1LnLKSnoIZsjdJ5XJ0+1WiXa
|
||||
pyTy5KBtKyhhLzy+toFeheNMbHV2yf2dFEFYM+76DKgXyMwNF1NzDUNhmxJrc/8H
|
||||
0qb4P/gFgWe1H3ZveY/iOyLzqmqBQw02blug+YHxLfC9Gc7Wz6aY86YxGg1bKjvi
|
||||
AG1GxtCKhUzBZMH7gGZSQJ+6Beq5cNReEgbSJu2N1QKBgQDnlz6cy22SMORCqUc9
|
||||
CO+NwzZeywruY8wjhZYx0kKqeC/g5Ama1+2THL7DVR+dgTJOuRvBgBtps/jfQKdz
|
||||
BpVeIfAH2n6x+62tjnHW274BwQy8SEf7nc4hvEY/PKUSqZ4JAeNISghHgfCMtcoW
|
||||
Lv7Ehbx+uA1AJPsGwIgCmlxBlQKBgQDEHKoZ2jeLlouYFsJlgEpqcLRQA+z8QsR7
|
||||
DSNjBZmuC4kKkQuoWhRoyvvgZIxT5xLkKG0eUh7kKsOdJ7Fv448rr6I9A03z9dDw
|
||||
bGrDl+vKlHSyo+9rfw8HXPG8G1b2X0K9LI8t6wBKHSPt8Bv5CXL9exJVQ33CndMW
|
||||
iIfpns0imwKBgANqyOK5YbGBhSyyoLl200oNMlUtu8iOsmlnxDKR/qfTRCmWU8n0
|
||||
G65LA0mQjPne+SYONymgwUbLAAYTRyU8WKHd8FO9Vpc7tnFUI7ve3CvcdFqm2mEN
|
||||
EAiRZZvzQiBHXmyVmYvsg7jCYxFAcW3oXZv6uTBJePCUWxvbZWZcbrYNAoGAcgWY
|
||||
gN93XBlzoEHbVNh6a9iLfdpKd4D6a/D/mhsvdxoN267pcECvjR43xAex7zZyrWUz
|
||||
zGVCwLZ8dWsWp09Pdr7vPTomoKlTifX/PSmfVnFqSFM4aO++9TD8+7mJnkVUsFiw
|
||||
BqqTyIOY2Ea6fNkZmndr+Vb8T6Mjj/5hx1slOfECgYEAttRU86Duvm8gypIux5xY
|
||||
CZeYiNCbZ/4+mR5yvfhVpMk2BJyOvtmoe6PnM+VLm+pNjZdK6mkPevXC6ownbyTl
|
||||
WOOmoxnl0lbZiKBmreNrdSKuAgezAATLQdipzuV8R4Udsko0lF03ZT1uMTBqI0Fs
|
||||
Kid3fvAzqQtoCLPyRsfKAr0=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+8VU37LjhtI2p
|
||||
/O8QCBdhFzzYv/+3VipAySD7TCCO6coHEs8PyV6z+uQ868PRmkyqBJgbW0/WdSt4
|
||||
RCTyCraTNMDIWEuoxbFHSLObT6fiMU9vlYcNlZkCMFIEqgd4r5657tQi1ROZ8gOD
|
||||
Jx3zpW9M5Yl5+jSDzLP7oAJWgbf3rTK/ePk4AU7SWAJGQXcD/C8RLsryyWE0Lps/
|
||||
X/B24GTXEj+u4Gp761KilDX8M3wLeLvYBb9DZ3rDwCXPJ8ukVc7ui5VZgGTOykVz
|
||||
u8YUnc0jJI7ptaOMv4oDniJ6aqfGaV8ghwo1xq3Rpbo0jOOeFgjOgM8JyCtbK/4r
|
||||
sRHiep3rAgMBAAECggEAH3x+V/2CMz3pymk6JsOez1TcpMVsbpgX2Z0RAj94cvic
|
||||
ZvQ0Dt9e7YDm2CDspoiyMasWRhSVosCpjWh3Sy53Euk1DRR6TXdkF2Qmseq9vW/y
|
||||
MG1Q2u2bUKAVNk2vc7hKDVETzDakx9L/v0XZC49xPhXvyJx4wm8kEs8883TqmD2/
|
||||
T5d7Xs7Rxkwo3iduqwHs3jWgJWwi44Il+ATfySAbymTZcPCXxOtWjvStAjgjtMEm
|
||||
KQYvvb0ZJIpqiPDH0Inq4D8U1AE0i16RlRteN6UB7avEP1v0SHFg1P2hTzWrwjZD
|
||||
lpgQ5JU6WO8tdtddMuR3u4hO5g6MJ2k5wkdLOjLTuQKBgQDm0ijy2/mCiqvKxaVp
|
||||
4hdAaTssHxT/+hDtWlcpqqYCgmNX7KRIzfTWLDg3qhENLcrxdBNt8bnhas2/E0/I
|
||||
IA8NyB9tkQlEXv5DYfUHG7ZFUndBgIftFRLM0Ibfx4A58PjQWwe9jtqSpQJdrEL6
|
||||
WRXsSBuveAqB4BTMtimUfxRH1wKBgQDTxYt1tij+x+ApVE4k//Vf+loKWOXDO/Bm
|
||||
R+O1e0mYEViEjpHd2QCEyy6JTYkTktO9yLt2A5rpZiDIA2wqnl0VMUXTbUOvsLNH
|
||||
UXOpy2rebWgEEZkTq/aFkhH4juLfimT6lJzZ18navPuFIj5Nj1emFU5NHQSOZ/EA
|
||||
6OPo5eXIDQKBgEDudd57yyDR6anNF89Fbs0LzT2IMNwheImMlGCARNsH2vJs+3oP
|
||||
lgR5xAbErK9MZn6t7JlNGsEyzlYmFJdzjUiPN2gXGMhHALfr4oXxYcD2hd3DTnl/
|
||||
KB69unNRJ90k0JmsQe0tNodyK8w2HVFXpjclwcQGvM30P2WnCONhLE9ZAoGAHcKz
|
||||
OJWi6Ts5m1VHrhdyakyKfs3DbE5uGFGeBJEQ5Jf7cpV+lki6s+7B2XXV/7QwoYkm
|
||||
Hw2epZI+pR0mBE9BEYtdHrtKOdSBPVKLCJ+Xoy6I4Zl/g6409Mx0ThP2eie+zSA5
|
||||
crvKmDzas/j9/HRagvKXkGq1izW8Pr572O0F/7kCgYANIGZ2YyGbuRdDxtNd1B/F
|
||||
MhK9Njbwee8FN5Emxyq/nJJe13gyNDRCmDqUlTDbCXVCyT3fWg6dLVZu0igf3Zf2
|
||||
+Z8lHBOD1VVBRdsYtRyZ/OvYksjOtBnZ5l4rMMt7MGxhA1Dm/oO/3ub0/dhrXnb+
|
||||
MD6TRIZ3GhAZ1SPeuhEF5w==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = SG
|
||||
ST = SG
|
||||
L = SG
|
||||
O = ESP32
|
||||
CN = ESP32 HTTPS Server
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = esp32-server
|
||||
DNS.2 = localhost
|
||||
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDhTCCAm2gAwIBAgIUeWALux8MJqS983GSSUHotYp9OxcwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMjUx
|
||||
MDEwMDIzOTAwWhcNMzUxMDA4MDIzOTAwWjBUMQswCQYDVQQGEwJTRzELMAkGA1UE
|
||||
CAwCU0cxCzAJBgNVBAcMAlNHMQ4wDAYDVQQKDAVFU1AzMjEbMBkGA1UEAwwSRVNQ
|
||||
MzIgSFRUUFMgU2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||
vvFVN+y44bSNqfzvEAgXYRc82L//t1YqQMkg+0wgjunKBxLPD8les/rkPOvD0ZpM
|
||||
qgSYG1tP1nUreEQk8gq2kzTAyFhLqMWxR0izm0+n4jFPb5WHDZWZAjBSBKoHeK+e
|
||||
ue7UItUTmfIDgycd86VvTOWJefo0g8yz+6ACVoG3960yv3j5OAFO0lgCRkF3A/wv
|
||||
ES7K8slhNC6bP1/wduBk1xI/ruBqe+tSopQ1/DN8C3i72AW/Q2d6w8AlzyfLpFXO
|
||||
7ouVWYBkzspFc7vGFJ3NIySO6bWjjL+KA54iemqnxmlfIIcKNcat0aW6NIzjnhYI
|
||||
zoDPCcgrWyv+K7ER4nqd6wIDAQABo34wfDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF
|
||||
4DAiBgNVHREEGzAZggxlc3AzMi1zZXJ2ZXKCCWxvY2FsaG9zdDAdBgNVHQ4EFgQU
|
||||
U2FQtf+ar/qQtk5hBObVuW/VAjMwHwYDVR0jBBgwFoAUyeZ0ko4R6lescj+K8zqZ
|
||||
WomABfwwDQYJKoZIhvcNAQELBQADggEBAHS3Nr8OTEWV6s5srMDxnvjry4XmBOU2
|
||||
gvNxLe7xci6DShCpFK+S+W/Vqmv0I17TEW5cV8Z/P5HKtiYAvNI7Ptc6BTK20Q83
|
||||
jKC6kSYMlK54zW4ZNbnvI/zwtMNh4YLiLVjSQxMe11qaTu7eOqI49qEmgNP82VPu
|
||||
JmFvO4W05McdUJ3xxVJVJk3l8ZTjgOTPfL/bbqibYSvewsyJlo2ihIHezP4Au/4Z
|
||||
2Wpzj1scBDSucDTsWBkXMGdV4tUpUuDYLJF2XqKwIm39IkoxdPnHANC62OtcLawh
|
||||
FOjfM34YqanizzWwfjNl6OD8vWP4ztIf3J4jONiDnnpVIo6skAGYTyQ=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
esp_stubs:
|
||||
path: ${IDF_PATH}/examples/protocols/linux_stubs/esp_stubs
|
||||
rules:
|
||||
- if: "target in [linux]"
|
||||
@@ -0,0 +1,324 @@
|
||||
/* Simple HTTP + SSL Server 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 <unistd.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_system.h>
|
||||
#include "esp_eth.h"
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
#include <esp_https_server.h>
|
||||
#include "esp_tls.h"
|
||||
#include "esp_key_config.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
|
||||
#include "mbedtls/ssl_ciphersuites.h"
|
||||
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
|
||||
|
||||
/* A simple example that demonstrates how to create GET and POST
|
||||
* handlers and start an HTTPS server.
|
||||
*/
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_EVENTS
|
||||
/* Event handler for catching system events */
|
||||
static void event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
if (event_base == ESP_HTTPS_SERVER_EVENT) {
|
||||
if (event_id == HTTPS_SERVER_EVENT_ERROR) {
|
||||
esp_https_server_last_error_t *last_error = (esp_tls_last_error_t *) event_data;
|
||||
ESP_LOGE(TAG, "Error event triggered: last_error = %s, last_tls_err = %d, tls_flag = %d", esp_err_to_name(last_error->last_error), last_error->esp_tls_error_code, last_error->esp_tls_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_ESP_HTTPS_SERVER_EVENTS
|
||||
|
||||
/* An HTTP GET handler */
|
||||
static esp_err_t root_get_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, "<h1>Hello Secure World!</h1>", HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK
|
||||
#ifdef CONFIG_ESP_TLS_USING_MBEDTLS
|
||||
static void print_peer_cert_info(const mbedtls_ssl_context *ssl)
|
||||
{
|
||||
const mbedtls_x509_crt *cert;
|
||||
const size_t buf_size = 1024;
|
||||
char *buf = calloc(buf_size, sizeof(char));
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE(TAG, "Out of memory - Callback execution failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Logging the peer certificate info
|
||||
cert = mbedtls_ssl_get_peer_cert(ssl);
|
||||
if (cert != NULL) {
|
||||
mbedtls_x509_crt_info((char *) buf, buf_size - 1, " ", cert);
|
||||
ESP_LOGI(TAG, "Peer certificate info:\n%s", buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not obtain the peer certificate!");
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* Example callback function to get the certificate of connected clients,
|
||||
* whenever a new SSL connection is created and closed
|
||||
*
|
||||
* Can also be used to other information like Socket FD, Connection state, etc.
|
||||
*
|
||||
* NOTE: This callback will not be able to obtain the client certificate if the
|
||||
* following config `Set minimum Certificate Verification mode to Optional` is
|
||||
* not enabled (enabled by default in this example).
|
||||
*
|
||||
* The config option is found here - Component config → ESP-TLS
|
||||
*
|
||||
*/
|
||||
static void https_server_user_callback(esp_https_server_user_cb_arg_t *user_cb)
|
||||
{
|
||||
ESP_LOGI(TAG, "User callback invoked!");
|
||||
#ifdef CONFIG_ESP_TLS_USING_MBEDTLS
|
||||
mbedtls_ssl_context *ssl_ctx = NULL;
|
||||
#endif
|
||||
switch(user_cb->user_cb_state) {
|
||||
case HTTPD_SSL_USER_CB_SESS_CREATE:
|
||||
ESP_LOGD(TAG, "At session creation");
|
||||
|
||||
// Logging the socket FD
|
||||
int sockfd = -1;
|
||||
esp_err_t esp_ret;
|
||||
esp_ret = esp_tls_get_conn_sockfd(user_cb->tls, &sockfd);
|
||||
if (esp_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Error in obtaining the sockfd from tls context");
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Socket FD: %d", sockfd);
|
||||
#ifdef CONFIG_ESP_TLS_USING_MBEDTLS
|
||||
ssl_ctx = (mbedtls_ssl_context *) esp_tls_get_ssl_context(user_cb->tls);
|
||||
if (ssl_ctx == NULL) {
|
||||
ESP_LOGE(TAG, "Error in obtaining ssl context");
|
||||
break;
|
||||
}
|
||||
// Logging the current ciphersuite
|
||||
ESP_LOGI(TAG, "Current Ciphersuite: %s", mbedtls_ssl_get_ciphersuite(ssl_ctx));
|
||||
#endif
|
||||
break;
|
||||
|
||||
case HTTPD_SSL_USER_CB_SESS_CLOSE:
|
||||
ESP_LOGD(TAG, "At session close");
|
||||
#ifdef CONFIG_ESP_TLS_USING_MBEDTLS
|
||||
// Logging the peer certificate
|
||||
ssl_ctx = (mbedtls_ssl_context *) esp_tls_get_ssl_context(user_cb->tls);
|
||||
if (ssl_ctx == NULL) {
|
||||
ESP_LOGE(TAG, "Error in obtaining ssl context");
|
||||
break;
|
||||
}
|
||||
print_peer_cert_info(ssl_ctx);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case HTTPD_SSL_USER_CB_SESS_ERROR:
|
||||
ESP_LOGD(TAG, "At session error (handshake failed)");
|
||||
// Get socket FD to log failing client IP address
|
||||
sockfd = -1;
|
||||
esp_ret = esp_tls_get_conn_sockfd(user_cb->tls, &sockfd);
|
||||
if (esp_ret == ESP_OK && sockfd >= 0) {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
if (getpeername(sockfd, (struct sockaddr*)&addr, &len) == 0) {
|
||||
char ip_str[INET6_ADDRSTRLEN];
|
||||
if (addr.ss_family == AF_INET) {
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in*)&addr)->sin_addr, ip_str, sizeof(ip_str));
|
||||
} else if (addr.ss_family == AF_INET6) {
|
||||
inet_ntop(AF_INET6, &((struct sockaddr_in6*)&addr)->sin6_addr, ip_str, sizeof(ip_str));
|
||||
} else {
|
||||
strcpy(ip_str, "unknown");
|
||||
}
|
||||
ESP_LOGW(TAG, "SSL handshake failed from client IP: %s", ip_str);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "Illegal state!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static const httpd_uri_t root = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = root_get_handler
|
||||
};
|
||||
|
||||
static httpd_handle_t start_webserver(void)
|
||||
{
|
||||
httpd_handle_t server = NULL;
|
||||
|
||||
// Start the httpd server
|
||||
ESP_LOGI(TAG, "Starting server");
|
||||
|
||||
httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* Use non-privileged port on Linux since port 443 requires root */
|
||||
conf.port_secure = 8443;
|
||||
#endif
|
||||
|
||||
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
|
||||
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
|
||||
|
||||
extern const unsigned char cacert_start[] asm("_binary_cacert_pem_start");
|
||||
extern const unsigned char cacert_end[] asm("_binary_cacert_pem_end");
|
||||
conf.servercert = servercert_start;
|
||||
conf.servercert_len = servercert_end - servercert_start;
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_SKIP_CLIENT_CERT
|
||||
conf.client_cert_authmode_optional = true;
|
||||
#endif // EXAMPLE_ENABLE_SKIP_CLIENT_CERT
|
||||
|
||||
conf.cacert_pem = cacert_start;
|
||||
conf.cacert_len = cacert_end - cacert_start;
|
||||
|
||||
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
|
||||
extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
|
||||
static esp_key_config_t server_key = {
|
||||
.source = ESP_KEY_SOURCE_BUFFER,
|
||||
.buffer = {
|
||||
.data = prvtkey_pem_start,
|
||||
}
|
||||
};
|
||||
server_key.buffer.len = prvtkey_pem_end - prvtkey_pem_start;
|
||||
conf.server_key = &server_key;
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
|
||||
static const int ciphersuites_to_use[] = {
|
||||
MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
0,
|
||||
};
|
||||
conf.ciphersuites_list = ciphersuites_to_use;
|
||||
#else
|
||||
conf.ciphersuites_list = NULL;
|
||||
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
|
||||
conf.tls_version = ESP_TLS_VER_TLS_1_3;
|
||||
#elif CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY
|
||||
conf.tls_version = ESP_TLS_VER_TLS_1_2;
|
||||
#else
|
||||
conf.tls_version = ESP_TLS_VER_ANY;
|
||||
#endif // CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY
|
||||
|
||||
#if CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK
|
||||
conf.user_cb = https_server_user_callback;
|
||||
#endif
|
||||
esp_err_t ret = httpd_ssl_start(&server, &conf);
|
||||
if (ESP_OK != ret) {
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &root);
|
||||
return server;
|
||||
}
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
static esp_err_t stop_webserver(httpd_handle_t server)
|
||||
{
|
||||
// Stop the httpd server
|
||||
return httpd_ssl_stop(server);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
if (stop_webserver(*server) == ESP_OK) {
|
||||
*server = NULL;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to stop https server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
*server = start_webserver();
|
||||
}
|
||||
}
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* Register event handlers to start server when Wi-Fi or Ethernet is connected,
|
||||
* and stop server when disconnection happens.
|
||||
*/
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_EVENTS
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ESP_HTTPS_SERVER_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
|
||||
#endif // CONFIG_ESP_HTTPS_SERVER_EVENTS
|
||||
|
||||
/* 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());
|
||||
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* On Linux, start the server directly since there are no WiFi/Ethernet events */
|
||||
server = start_webserver();
|
||||
while (server) {
|
||||
sleep(5);
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_LINUX
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import http.client
|
||||
import logging
|
||||
import os
|
||||
import ssl
|
||||
|
||||
import pytest
|
||||
from common_test_methods import get_env_config_variable
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
CURRENT_FILE_PATH = os.path.dirname(__file__)
|
||||
|
||||
CACERT_FILE_PATH = os.path.join(CURRENT_FILE_PATH, './main/certs/cacert.pem')
|
||||
CLIENT_CERT_PATH = os.path.join(CURRENT_FILE_PATH, './main/certs/client_cert.pem')
|
||||
CLIENT_KEY_PATH = os.path.join(CURRENT_FILE_PATH, './main/certs/client_key.pem')
|
||||
|
||||
success_response = '<h1>Hello Secure World!</h1>'
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3'], indirect=['target'])
|
||||
def test_examples_protocol_https_server_simple(dut: Dut) -> None:
|
||||
"""
|
||||
steps: |
|
||||
1. join AP
|
||||
2. connect to www.howsmyssl.com:443
|
||||
3. send http request
|
||||
"""
|
||||
# check and log bin size
|
||||
binary_file = os.path.join(dut.app.binary_path, 'https_server.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info(f'https_server_simple_bin_size : {bin_size // 1024}KB')
|
||||
# start test
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address and port of the server
|
||||
dut.expect(r'Starting server')
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
# Expected logs
|
||||
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
|
||||
# Try requesting without client certificate and it should fail if client cert is required
|
||||
logging.info('Performing GET request over an SSL connection with the server without client certificate')
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
# Load the CA certificate from the path
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server without client certificate')
|
||||
try:
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
resp.read().decode('utf-8')
|
||||
except Exception as e:
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_SKIP_CLIENT_CERT') is True:
|
||||
logging.info('SSL handshake failed without client certificate but was expected to be successful')
|
||||
raise RuntimeError('Failed to test SSL connection') from e
|
||||
else:
|
||||
logging.info('SSL handshake failed without client certificate as expected')
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
logging.info('Performing GET request over an SSL connection with the server')
|
||||
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
|
||||
ssl_context.load_cert_chain(certfile=CLIENT_CERT_PATH, keyfile=CLIENT_KEY_PATH)
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server')
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
dut.expect('performing session handshake')
|
||||
got_resp = resp.read().decode('utf-8')
|
||||
if got_resp != success_response:
|
||||
logging.info('Response obtained does not match with correct response')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
|
||||
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
|
||||
logging.info(f'Current Ciphersuite {current_cipher}')
|
||||
|
||||
conn.close()
|
||||
|
||||
logging.info('Checking user callback: Obtaining client certificate...')
|
||||
|
||||
serial_number = dut.expect(r'serial number\s*:([^\n]*)', timeout=5)[0]
|
||||
issuer_name = dut.expect(r'issuer name\s*:([^\n]*)', timeout=5)[0]
|
||||
expiry = dut.expect(r'expires on ((.*)\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])*)', timeout=5)[
|
||||
1
|
||||
].decode()
|
||||
|
||||
logging.info(f'Serial No. {serial_number}')
|
||||
logging.info(f'Issuer Name {issuer_name}')
|
||||
logging.info(f'Expires on {expiry}')
|
||||
logging.info('Correct response obtained')
|
||||
logging.info('SSL connection test successful\nClosing the connection')
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'dynamic_buffer',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3'], indirect=['target'])
|
||||
def test_examples_protocol_https_server_simple_dynamic_buffers(dut: Dut) -> None:
|
||||
# Test with mbedTLS dynamic buffer feature
|
||||
|
||||
# start test
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address and port of the server
|
||||
dut.expect(r'Starting server')
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
# Expected logs
|
||||
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
|
||||
logging.info('Performing GET request over an SSL connection with the server')
|
||||
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
|
||||
ssl_context.load_cert_chain(certfile=CLIENT_CERT_PATH, keyfile=CLIENT_KEY_PATH)
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server')
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
dut.expect('performing session handshake')
|
||||
got_resp = resp.read().decode('utf-8')
|
||||
if got_resp != success_response:
|
||||
logging.info('Response obtained does not match with correct response')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
|
||||
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
|
||||
logging.info(f'Current Ciphersuite {current_cipher}')
|
||||
|
||||
# Close the connection
|
||||
conn.close()
|
||||
|
||||
logging.info('Checking user callback: Obtaining client certificate...')
|
||||
|
||||
serial_number = dut.expect(r'serial number\s*:([^\n]*)', timeout=5)[0]
|
||||
issuer_name = dut.expect(r'issuer name\s*:([^\n]*)', timeout=5)[0]
|
||||
expiry = dut.expect(r'expires on\s*:((.*)\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])*)', timeout=5)[
|
||||
1
|
||||
].decode()
|
||||
|
||||
logging.info(f'Serial No. : {serial_number}')
|
||||
logging.info(f'Issuer Name : {issuer_name}')
|
||||
logging.info(f'Expires on : {expiry}')
|
||||
|
||||
logging.info('Correct response obtained')
|
||||
logging.info('SSL connection test successful\nClosing the connection')
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'tls1_3',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3'], indirect=['target'])
|
||||
def test_examples_protocol_https_server_tls1_3(dut: Dut) -> None:
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address and port of the server
|
||||
dut.expect(r'Starting server')
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
# Expected logs
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
logging.info('Performing GET request over an SSL connection with the server using TLSv1.3')
|
||||
|
||||
# First try requesting without client certificate and it should also pass if client cert is optional or none
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server without client certificate')
|
||||
try:
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
resp.read().decode('utf-8')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_SKIP_CLIENT_CERT') is True:
|
||||
dut.expect('performing session handshake')
|
||||
logging.info('SSL handshake successful without client certificate as expected')
|
||||
else:
|
||||
logging.info('SSL handshake successful without client certificate but was expected to fail')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
except Exception as e:
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_SKIP_CLIENT_CERT') is True:
|
||||
logging.info(f'SSL handshake failed without client certificate but was expected to be successful: {e}')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
else:
|
||||
logging.info('SSL handshake failed without client certificate as expected')
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# First try with TLSv1.2 and that should fail
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_2
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
ssl_context.load_cert_chain(certfile=CLIENT_CERT_PATH, keyfile=CLIENT_KEY_PATH)
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
try:
|
||||
conn.request('GET', '/')
|
||||
except ssl.SSLError as e:
|
||||
logging.info(f'SSL handshake failed with TLSv1.2: {e}')
|
||||
else:
|
||||
logging.info('SSL handshake succeeded with TLSv1.2')
|
||||
raise RuntimeError('This should have failed')
|
||||
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server')
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
dut.expect('performing session handshake')
|
||||
got_resp = resp.read().decode('utf-8')
|
||||
if got_resp != success_response:
|
||||
logging.info('Response obtained does not match with correct response')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
|
||||
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
|
||||
logging.info(f'Current Ciphersuite {current_cipher}')
|
||||
|
||||
conn.close()
|
||||
|
||||
logging.info('Checking user callback: Obtaining client certificate...')
|
||||
|
||||
serial_number = dut.expect(r'serial number\s*:([^\n]*)', timeout=5)[0]
|
||||
issuer_name = dut.expect(r'issuer name\s*:([^\n]*)', timeout=5)[0]
|
||||
expiry = dut.expect(
|
||||
r'expires on\s*:((.*)\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])*)',
|
||||
timeout=5,
|
||||
)[1].decode()
|
||||
|
||||
logging.info(f'Serial No. : {serial_number}')
|
||||
logging.info(f'Issuer Name : {issuer_name}')
|
||||
logging.info(f'Expires on : {expiry}')
|
||||
|
||||
logging.info('Correct response obtained')
|
||||
logging.info('SSL connection test successful\nClosing the connection')
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'tls1_2_only',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3'], indirect=['target'])
|
||||
def test_examples_protocol_https_server_tls1_2_only(dut: Dut) -> None:
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address and port of the server
|
||||
dut.expect(r'Starting server')
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
# Expected logs
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
logging.info('Performing GET request over an SSL connection with the server using TLSv1.2')
|
||||
|
||||
# First try with TLSv1.3 and that should fail
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_3
|
||||
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.load_verify_locations(cafile=CACERT_FILE_PATH)
|
||||
# ssl_context.load_verify_locations(cadata=server_cert_pem)
|
||||
ssl_context.load_cert_chain(certfile=CLIENT_CERT_PATH, keyfile=CLIENT_KEY_PATH)
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
try:
|
||||
conn.request('GET', '/')
|
||||
except ssl.SSLError as e:
|
||||
logging.info(f'SSL handshake failed with TLSv1.3: {e}')
|
||||
else:
|
||||
logging.info('SSL handshake succeeded with TLSv1.3')
|
||||
raise RuntimeError('This should have failed')
|
||||
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
ssl_context.maximum_version = ssl.TLSVersion.TLSv1_2
|
||||
|
||||
# Also now with TLS1.2, try with a non matching ciphersuite and that should fail
|
||||
# Server only accepts: DHE-RSA-AES128-SHA256, DHE-RSA-AES256-SHA256,
|
||||
# ECDHE-RSA-AES256-SHA384, ECDHE-RSA-AES128-SHA256
|
||||
# Try AES128-GCM-SHA256 which is NOT in the list
|
||||
ssl_context.set_ciphers('AES128-GCM-SHA256')
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
try:
|
||||
logging.info('Trying SSL handshake with non-matching ciphersuite (should fail)')
|
||||
conn.request('GET', '/')
|
||||
except ssl.SSLError as e:
|
||||
logging.info(f'SSL handshake failed with non-matching ciphersuite (expected): {e}')
|
||||
else:
|
||||
logging.info('SSL handshake succeeded with non-matching ciphersuite')
|
||||
raise RuntimeError('This should have failed - custom ciphersuites not enforced')
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Now try with the matching ciphersuite
|
||||
ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256')
|
||||
|
||||
conn = http.client.HTTPSConnection(got_ip, got_port, context=ssl_context)
|
||||
logging.info('Performing SSL handshake with the server')
|
||||
conn.request('GET', '/')
|
||||
resp = conn.getresponse()
|
||||
dut.expect('performing session handshake')
|
||||
got_resp = resp.read().decode('utf-8')
|
||||
if got_resp != success_response:
|
||||
logging.info('Response obtained does not match with correct response')
|
||||
raise RuntimeError('Failed to test SSL connection')
|
||||
|
||||
if dut.app.sdkconfig.get('EXAMPLE_ENABLE_HTTPS_USER_CALLBACK') is True:
|
||||
current_cipher = dut.expect(r'Current Ciphersuite(.*)', timeout=5)[0]
|
||||
logging.info(f'Current Ciphersuite {current_cipher}')
|
||||
|
||||
logging.info('Checking user callback: Obtaining client certificate...')
|
||||
|
||||
serial_number = dut.expect(r'serial number\s*:([^\n]*)', timeout=5)[0]
|
||||
issuer_name = dut.expect(r'issuer name\s*:([^\n]*)', timeout=5)[0]
|
||||
expiry = dut.expect(
|
||||
r'expires on\s*:((.*)\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])*)',
|
||||
timeout=5,
|
||||
)[1].decode()
|
||||
|
||||
logging.info(f'Serial No. : {serial_number}')
|
||||
logging.info(f'Issuer Name : {issuer_name}')
|
||||
logging.info(f'Expires on : {expiry}')
|
||||
|
||||
logging.info('Correct response obtained')
|
||||
logging.info('SSL connection test successful\nClosing the connection')
|
||||
@@ -0,0 +1,5 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK=y
|
||||
CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y
|
||||
@@ -0,0 +1,7 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_EXAMPLE_ENABLE_HTTPS_USER_CALLBACK=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_ESP_TLS_SERVER_SESSION_TICKETS=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_2_ONLY=y
|
||||
CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_CUSTOM_CIPHERSUITES=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y
|
||||
@@ -0,0 +1,11 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK=y
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
|
||||
CONFIG_MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE=y
|
||||
CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_PSK=y
|
||||
CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_EPHEMERAL=y
|
||||
CONFIG_MBEDTLS_SSL_TLS1_3_KEXM_PSK_EPHEMERAL=y
|
||||
CONFIG_EXAMPLE_ENABLE_HTTPS_SERVER_TLS_1_3_ONLY=y
|
||||
CONFIG_EXAMPLE_ENABLE_SKIP_CLIENT_CERT=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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.22)
|
||||
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(wss_server)
|
||||
@@ -0,0 +1,64 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# HTTPS Websocket server
|
||||
|
||||
This example creates an HTTPS server with SSL/TLS support using ESP-TLS and employs a simple Websocket request handler. It demonstrates handling multiple clients from the server including:
|
||||
* PING-PONG mechanism
|
||||
* Sending asynchronous messages to all clients
|
||||
|
||||
For more information, refer to the [esp_https_server component documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_https_server.html).
|
||||
|
||||
|
||||
### Websocket support in `http_server`
|
||||
|
||||
Please refer to the documentation of [Websocket server](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/esp_http_server.html#websocket-server) feature in the documentation,
|
||||
or to the description of using websocket handlers in httpd in the [simple ws echo](../../http_server/ws_echo_server/README.md#how-to-use-example) example.
|
||||
|
||||
## How to use example
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for power supply and programming
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
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.
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Certificates
|
||||
|
||||
You will need to approve a security exception in your browser. This is because of a self signed
|
||||
certificate; this will be always the case, unless you preload the CA root into your browser/system
|
||||
as trusted.
|
||||
|
||||
You can generate a new certificate using the OpenSSL command line tool:
|
||||
|
||||
```
|
||||
openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -x509 -days 3650 -out servercert.pem -subj "/CN=ESP32 HTTPS server example" -addext "keyUsage=critical,digitalSignature,keyCertSign"
|
||||
```
|
||||
|
||||
Expiry time and metadata fields can be adjusted in the invocation.
|
||||
|
||||
Please see the openssl man pages (man openssl-req) for more details.
|
||||
|
||||
It is **strongly recommended** to not reuse the example certificate in your application;
|
||||
it is included only for demonstration.
|
||||
@@ -0,0 +1,14 @@
|
||||
set(requires esp_https_server nvs_flash esp_timer esp_netif)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND requires esp_stubs protocol_examples_common)
|
||||
else()
|
||||
list(APPEND requires esp_wifi esp_eth)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "wss_server_example.c" "keep_alive.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES ${requires}
|
||||
EMBED_TXTFILES "certs/servercert.pem"
|
||||
"certs/prvtkey.pem")
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
|
||||
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
|
||||
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
|
||||
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
|
||||
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
|
||||
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
|
||||
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
|
||||
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
|
||||
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
|
||||
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
|
||||
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
|
||||
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
|
||||
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
|
||||
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
|
||||
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
|
||||
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
|
||||
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
|
||||
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
|
||||
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
|
||||
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
|
||||
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
|
||||
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
|
||||
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
|
||||
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
|
||||
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
|
||||
v/t+MeGJP/0Zw8v/X2CFll96
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDOzCCAiOgAwIBAgIUGtx0JiogvT3DlTnZ3+tAT7Tr5JEwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMjUw
|
||||
NDAyMDcwNzE1WhcNMzUwMzMxMDcwNzE1WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
|
||||
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
|
||||
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
|
||||
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
|
||||
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
|
||||
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
|
||||
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNjMGEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
|
||||
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQAT
|
||||
lpY3s1IAV369xl7cri72ErqFRBveKvJCaq/1l0FSH1w/u3SxABQw9SH29Lg0hbAa
|
||||
jotcCMo4XZpmKSsQn0Zs4ZgKh1zk90JZVtssWuU3y8ftq9t4JjskRHTF19yp7CCC
|
||||
zKIHPlCyYjCgw3tWhrsWa95dF3ebVrPkuUfd6CCxe8OB4EC74svI+uuxA7Ud9Jtx
|
||||
Dno5yFu7NKpRLSwFqJnxbU0bZp434v2vcexkbvZP0Z2AW+C2L+x90xcP5rW2vL+a
|
||||
S0bj0quNH43cGKbBFzE9cLy04xsGlOqzYSQcFbGeT9LyQBLFZtp6QlC97ZsQ78fG
|
||||
A2PRPwcF0QCvQrT42b8y
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
esp_stubs:
|
||||
path: ${IDF_PATH}/examples/protocols/linux_stubs/esp_stubs
|
||||
rules:
|
||||
- if: "target in [linux]"
|
||||
@@ -0,0 +1,239 @@
|
||||
/* Keep Alive engine for wss server 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 <esp_log.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/task.h"
|
||||
#include "keep_alive.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_system.h>
|
||||
#include "esp_timer.h"
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
typedef enum {
|
||||
NO_CLIENT = 0,
|
||||
CLIENT_FD_ADD,
|
||||
CLIENT_FD_REMOVE,
|
||||
CLIENT_UPDATE,
|
||||
CLIENT_ACTIVE,
|
||||
STOP_TASK,
|
||||
} client_fd_action_type_t;
|
||||
|
||||
typedef struct {
|
||||
client_fd_action_type_t type;
|
||||
int fd;
|
||||
uint64_t last_seen;
|
||||
} client_fd_action_t;
|
||||
|
||||
typedef struct wss_keep_alive_storage {
|
||||
size_t max_clients;
|
||||
wss_check_client_alive_cb_t check_client_alive_cb;
|
||||
wss_check_client_alive_cb_t client_not_alive_cb;
|
||||
size_t keep_alive_period_ms;
|
||||
size_t not_alive_after_ms;
|
||||
void * user_ctx;
|
||||
QueueHandle_t q;
|
||||
client_fd_action_t clients[];
|
||||
} wss_keep_alive_storage_t;
|
||||
|
||||
typedef struct wss_keep_alive_storage* wss_keep_alive_t;
|
||||
|
||||
static const char *TAG = "wss_keep_alive";
|
||||
|
||||
static uint64_t _tick_get_ms(void)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
#else
|
||||
return esp_timer_get_time() / 1000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Goes over active clients to find out how long we could sleep before checking who's alive
|
||||
static uint64_t get_max_delay(wss_keep_alive_t h)
|
||||
{
|
||||
int64_t check_after_ms = 30000; // max delay, no need to check anyone
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE) {
|
||||
uint64_t check_this_client_at = h->clients[i].last_seen + h->keep_alive_period_ms;
|
||||
if (check_this_client_at < check_after_ms + _tick_get_ms()) {
|
||||
check_after_ms = check_this_client_at - _tick_get_ms();
|
||||
if (check_after_ms < 0) {
|
||||
check_after_ms = 1000; // min delay, some client(s) not responding already
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return check_after_ms;
|
||||
}
|
||||
|
||||
|
||||
static bool update_client(wss_keep_alive_t h, int sockfd, uint64_t timestamp)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
|
||||
h->clients[i].last_seen = timestamp;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool remove_client(wss_keep_alive_t h, int sockfd)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == CLIENT_ACTIVE && h->clients[i].fd == sockfd) {
|
||||
h->clients[i].type = NO_CLIENT;
|
||||
h->clients[i].fd = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static bool add_new_client(wss_keep_alive_t h,int sockfd)
|
||||
{
|
||||
for (int i=0; i<h->max_clients; ++i) {
|
||||
if (h->clients[i].type == NO_CLIENT) {
|
||||
h->clients[i].type = CLIENT_ACTIVE;
|
||||
h->clients[i].fd = sockfd;
|
||||
h->clients[i].last_seen = _tick_get_ms();
|
||||
return true; // success
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void keep_alive_task(void* arg)
|
||||
{
|
||||
wss_keep_alive_storage_t *keep_alive_storage = arg;
|
||||
bool run_task = true;
|
||||
client_fd_action_t client_action;
|
||||
while (run_task) {
|
||||
if (xQueueReceive(keep_alive_storage->q, (void *) &client_action,
|
||||
get_max_delay(keep_alive_storage) / portTICK_PERIOD_MS) == pdTRUE) {
|
||||
switch (client_action.type) {
|
||||
case CLIENT_FD_ADD:
|
||||
if (!add_new_client(keep_alive_storage, client_action.fd)) {
|
||||
ESP_LOGE(TAG, "Cannot add new client");
|
||||
}
|
||||
break;
|
||||
case CLIENT_FD_REMOVE:
|
||||
if (!remove_client(keep_alive_storage, client_action.fd)) {
|
||||
ESP_LOGE(TAG, "Cannot remove client fd:%d", client_action.fd);
|
||||
}
|
||||
break;
|
||||
case CLIENT_UPDATE:
|
||||
if (!update_client(keep_alive_storage, client_action.fd, client_action.last_seen)) {
|
||||
ESP_LOGE(TAG, "Cannot find client fd:%d", client_action.fd);
|
||||
}
|
||||
break;
|
||||
case STOP_TASK:
|
||||
run_task = false;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unexpected client action");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// timeout: check if PING message needed
|
||||
for (int i=0; i<keep_alive_storage->max_clients; ++i) {
|
||||
if (keep_alive_storage->clients[i].type == CLIENT_ACTIVE) {
|
||||
if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->keep_alive_period_ms <= _tick_get_ms()) {
|
||||
ESP_LOGD(TAG, "Haven't seen the client (fd=%d) for a while", keep_alive_storage->clients[i].fd);
|
||||
if (keep_alive_storage->clients[i].last_seen + keep_alive_storage->not_alive_after_ms <= _tick_get_ms()) {
|
||||
ESP_LOGE(TAG, "Client (fd=%d) not alive!", keep_alive_storage->clients[i].fd);
|
||||
keep_alive_storage->client_not_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
|
||||
} else {
|
||||
keep_alive_storage->check_client_alive_cb(keep_alive_storage, keep_alive_storage->clients[i].fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vQueueDelete(keep_alive_storage->q);
|
||||
free(keep_alive_storage);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config)
|
||||
{
|
||||
size_t queue_size = config->max_clients/2;
|
||||
size_t client_list_size = config->max_clients + queue_size;
|
||||
wss_keep_alive_t keep_alive_storage = calloc(1,
|
||||
sizeof(wss_keep_alive_storage_t) + client_list_size* sizeof(client_fd_action_t));
|
||||
if (keep_alive_storage == NULL) {
|
||||
return false;
|
||||
}
|
||||
keep_alive_storage->check_client_alive_cb = config->check_client_alive_cb;
|
||||
keep_alive_storage->client_not_alive_cb = config->client_not_alive_cb;
|
||||
keep_alive_storage->max_clients = config->max_clients;
|
||||
keep_alive_storage->not_alive_after_ms = config->not_alive_after_ms;
|
||||
keep_alive_storage->keep_alive_period_ms = config->keep_alive_period_ms;
|
||||
keep_alive_storage->user_ctx = config->user_ctx;
|
||||
keep_alive_storage->q = xQueueCreate(queue_size, sizeof(client_fd_action_t));
|
||||
if (xTaskCreate(keep_alive_task, "keep_alive_task", config->task_stack_size,
|
||||
keep_alive_storage, config->task_prio, NULL) != pdTRUE) {
|
||||
wss_keep_alive_stop(keep_alive_storage);
|
||||
return false;
|
||||
}
|
||||
return keep_alive_storage;
|
||||
}
|
||||
|
||||
void wss_keep_alive_stop(wss_keep_alive_t h)
|
||||
{
|
||||
client_fd_action_t stop = { .type = STOP_TASK };
|
||||
xQueueSendToBack(h->q, &stop, 0);
|
||||
// internal structs will be de-allocated in the task
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_ADD};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_FD_REMOVE};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
client_fd_action_t client_fd_action = { .fd = fd, .type = CLIENT_UPDATE,
|
||||
.last_seen = _tick_get_ms()};
|
||||
if (xQueueSendToBack(h->q, &client_fd_action, 0) == pdTRUE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
|
||||
}
|
||||
|
||||
void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx)
|
||||
{
|
||||
h->user_ctx = ctx;
|
||||
}
|
||||
|
||||
void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h)
|
||||
{
|
||||
return h->user_ctx;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/* Keep Alive engine for wss server 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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define KEEP_ALIVE_CONFIG_DEFAULT() \
|
||||
{ \
|
||||
.max_clients = 10, \
|
||||
.task_stack_size = 2048, \
|
||||
.task_prio = tskIDLE_PRIORITY+1, \
|
||||
.keep_alive_period_ms = 5000, \
|
||||
.not_alive_after_ms = 10000, \
|
||||
}
|
||||
|
||||
struct wss_keep_alive_storage;
|
||||
typedef struct wss_keep_alive_storage* wss_keep_alive_t;
|
||||
typedef bool (*wss_check_client_alive_cb_t)(wss_keep_alive_t h, int fd);
|
||||
typedef bool (*wss_client_not_alive_cb_t)(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Confiuration struct
|
||||
*/
|
||||
typedef struct {
|
||||
size_t max_clients; /*!< max number of clients */
|
||||
size_t task_stack_size; /*!< stack size of the created task */
|
||||
size_t task_prio; /*!< priority of the created task */
|
||||
size_t keep_alive_period_ms; /*!< check every client after this time */
|
||||
size_t not_alive_after_ms; /*!< consider client not alive after this time */
|
||||
wss_check_client_alive_cb_t check_client_alive_cb; /*!< callback function to check if client is alive */
|
||||
wss_client_not_alive_cb_t client_not_alive_cb; /*!< callback function to notify that the client is not alive */
|
||||
void *user_ctx; /*!< user context available in the keep-alive handle */
|
||||
} wss_keep_alive_config_t;
|
||||
|
||||
/**
|
||||
* @brief Adds a new client to internal set of clients to keep an eye on
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wss_keep_alive_add_client(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Removes this client from the set
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t wss_keep_alive_remove_client(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Notify that this client is alive
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param fd socket file descriptor for this client
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
|
||||
esp_err_t wss_keep_alive_client_is_active(wss_keep_alive_t h, int fd);
|
||||
|
||||
/**
|
||||
* @brief Starts keep-alive engine
|
||||
*
|
||||
* @param config keep-alive configuration
|
||||
* @return keep alive handle
|
||||
*/
|
||||
wss_keep_alive_t wss_keep_alive_start(wss_keep_alive_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Stops keep-alive engine
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
*/
|
||||
void wss_keep_alive_stop(wss_keep_alive_t h);
|
||||
|
||||
/**
|
||||
* @brief Sets user defined context
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @param ctx user context
|
||||
*/
|
||||
void wss_keep_alive_set_user_ctx(wss_keep_alive_t h, void *ctx);
|
||||
|
||||
/**
|
||||
* @brief Gets user defined context
|
||||
*
|
||||
* @param h keep-alive handle
|
||||
* @return ctx user context
|
||||
*/
|
||||
void* wss_keep_alive_get_user_ctx(wss_keep_alive_t h);
|
||||
@@ -0,0 +1,345 @@
|
||||
/* Simple HTTP + SSL + WS Server 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 <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_system.h>
|
||||
#include "esp_eth.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "lwip/sockets.h"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
#include <esp_https_server.h>
|
||||
#include "esp_key_config.h"
|
||||
#include "keep_alive.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error This example cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||
#endif
|
||||
|
||||
struct async_resp_arg {
|
||||
httpd_handle_t hd;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static const char *TAG = "wss_echo_server";
|
||||
static const size_t max_clients = 4;
|
||||
|
||||
static esp_err_t ws_handler(httpd_req_t *req)
|
||||
{
|
||||
if (req->method == HTTP_GET) {
|
||||
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
|
||||
return ESP_OK;
|
||||
}
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
uint8_t *buf = NULL;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
|
||||
// First receive the full ws message
|
||||
/* Set max_len = 0 to get the frame len */
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
|
||||
if (ws_pkt.len) {
|
||||
/* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
|
||||
buf = calloc(1, ws_pkt.len + 1);
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to calloc memory for buf");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ws_pkt.payload = buf;
|
||||
/* Set max_len = ws_pkt.len to get the frame payload */
|
||||
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
// If it was a PONG, update the keep-alive
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_PONG) {
|
||||
ESP_LOGD(TAG, "Received PONG message");
|
||||
free(buf);
|
||||
return wss_keep_alive_client_is_active(httpd_get_global_user_ctx(req->handle),
|
||||
httpd_req_to_sockfd(req));
|
||||
|
||||
// If it was a TEXT message, just echo it back
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_PING || ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
|
||||
ESP_LOGI(TAG, "Received packet with message: %s", ws_pkt.payload);
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_PING) {
|
||||
// Response PONG packet to peer
|
||||
ESP_LOGI(TAG, "Got a WS PING frame, Replying PONG");
|
||||
ws_pkt.type = HTTPD_WS_TYPE_PONG;
|
||||
} else if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
|
||||
// Response CLOSE packet with no payload to peer
|
||||
ws_pkt.len = 0;
|
||||
ws_pkt.payload = NULL;
|
||||
}
|
||||
ret = httpd_ws_send_frame(req, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
|
||||
}
|
||||
ESP_LOGI(TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", req->handle,
|
||||
httpd_req_to_sockfd(req), httpd_ws_get_fd_info(req->handle, httpd_req_to_sockfd(req)));
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
free(buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t wss_open_fd(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGI(TAG, "New client connected %d", sockfd);
|
||||
wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
|
||||
return wss_keep_alive_add_client(h, sockfd);
|
||||
}
|
||||
|
||||
void wss_close_fd(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGI(TAG, "Client disconnected %d", sockfd);
|
||||
wss_keep_alive_t h = httpd_get_global_user_ctx(hd);
|
||||
wss_keep_alive_remove_client(h, sockfd);
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
static const httpd_uri_t ws = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true,
|
||||
.handle_ws_control_frames = true
|
||||
};
|
||||
|
||||
|
||||
static void send_hello(void *arg)
|
||||
{
|
||||
static const char * data = "Hello client";
|
||||
struct async_resp_arg *resp_arg = arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = (uint8_t*)data;
|
||||
ws_pkt.len = strlen(data);
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
|
||||
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
|
||||
free(resp_arg);
|
||||
}
|
||||
|
||||
static void send_ping(void *arg)
|
||||
{
|
||||
struct async_resp_arg *resp_arg = arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = NULL;
|
||||
ws_pkt.len = 0;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_PING;
|
||||
|
||||
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
|
||||
free(resp_arg);
|
||||
}
|
||||
|
||||
bool client_not_alive_cb(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
ESP_LOGE(TAG, "Client not alive, closing fd %d", fd);
|
||||
httpd_sess_trigger_close(wss_keep_alive_get_user_ctx(h), fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_client_alive_cb(wss_keep_alive_t h, int fd)
|
||||
{
|
||||
ESP_LOGD(TAG, "Checking if client (fd=%d) is alive", fd);
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
assert(resp_arg != NULL);
|
||||
resp_arg->hd = wss_keep_alive_get_user_ctx(h);
|
||||
resp_arg->fd = fd;
|
||||
|
||||
if (httpd_queue_work(resp_arg->hd, send_ping, resp_arg) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static httpd_handle_t start_wss_echo_server(void)
|
||||
{
|
||||
// Prepare keep-alive engine
|
||||
wss_keep_alive_config_t keep_alive_config = KEEP_ALIVE_CONFIG_DEFAULT();
|
||||
keep_alive_config.max_clients = max_clients;
|
||||
keep_alive_config.client_not_alive_cb = client_not_alive_cb;
|
||||
keep_alive_config.check_client_alive_cb = check_client_alive_cb;
|
||||
wss_keep_alive_t keep_alive = wss_keep_alive_start(&keep_alive_config);
|
||||
|
||||
// Start the httpd server
|
||||
httpd_handle_t server = NULL;
|
||||
ESP_LOGI(TAG, "Starting server");
|
||||
|
||||
httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* Use non-privileged port on Linux since port 443 requires root */
|
||||
conf.port_secure = 8443;
|
||||
#endif
|
||||
conf.httpd.max_open_sockets = max_clients;
|
||||
conf.httpd.global_user_ctx = keep_alive;
|
||||
conf.httpd.open_fn = wss_open_fd;
|
||||
conf.httpd.close_fn = wss_close_fd;
|
||||
|
||||
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
|
||||
extern const unsigned char servercert_end[] asm("_binary_servercert_pem_end");
|
||||
conf.servercert = servercert_start;
|
||||
conf.servercert_len = servercert_end - servercert_start;
|
||||
|
||||
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
|
||||
extern const unsigned char prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
|
||||
static esp_key_config_t server_key = {
|
||||
.source = ESP_KEY_SOURCE_BUFFER,
|
||||
.buffer = {
|
||||
.data = prvtkey_pem_start,
|
||||
}
|
||||
};
|
||||
server_key.buffer.len = prvtkey_pem_end - prvtkey_pem_start;
|
||||
conf.server_key = &server_key;
|
||||
|
||||
esp_err_t ret = httpd_ssl_start(&server, &conf);
|
||||
if (ESP_OK != ret) {
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
wss_keep_alive_set_user_ctx(keep_alive, server);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
static esp_err_t stop_wss_echo_server(httpd_handle_t server)
|
||||
{
|
||||
// Stop keep alive thread
|
||||
wss_keep_alive_stop(httpd_get_global_user_ctx(server));
|
||||
// Stop the httpd server
|
||||
return httpd_ssl_stop(server);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
if (stop_wss_echo_server(*server) == ESP_OK) {
|
||||
*server = NULL;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to stop https server");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
*server = start_wss_echo_server();
|
||||
}
|
||||
}
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
// Get all clients and send async message
|
||||
static void wss_server_send_messages(httpd_handle_t* server)
|
||||
{
|
||||
bool send_messages = true;
|
||||
|
||||
// Send async message to all connected clients that use websocket protocol every 10 seconds
|
||||
while (send_messages) {
|
||||
vTaskDelay(10000 / portTICK_PERIOD_MS);
|
||||
|
||||
if (!*server) { // httpd might not have been created by now
|
||||
continue;
|
||||
}
|
||||
size_t clients = max_clients;
|
||||
int client_fds[max_clients];
|
||||
if (httpd_get_client_list(*server, &clients, client_fds) == ESP_OK) {
|
||||
for (size_t i=0; i < clients; ++i) {
|
||||
int sock = client_fds[i];
|
||||
if (httpd_ws_get_fd_info(*server, sock) == HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
ESP_LOGI(TAG, "Active client (fd=%d) -> sending async message", sock);
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
assert(resp_arg != NULL);
|
||||
resp_arg->hd = *server;
|
||||
resp_arg->fd = sock;
|
||||
if (httpd_queue_work(resp_arg->hd, send_hello, resp_arg) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_queue_work failed!");
|
||||
send_messages = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "httpd_get_client_list failed!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* Register event handlers to start server when Wi-Fi or Ethernet is connected,
|
||||
* and stop server when disconnection happens.
|
||||
*/
|
||||
|
||||
#if !CONFIG_IDF_TARGET_LINUX
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
#endif // !CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
/* 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());
|
||||
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
/* On Linux, start the server directly since there are no WiFi/Ethernet events */
|
||||
server = start_wss_echo_server();
|
||||
#endif // CONFIG_IDF_TARGET_LINUX
|
||||
|
||||
/* This function demonstrates periodic sending Websocket messages
|
||||
* to all connected clients to this server
|
||||
*/
|
||||
wss_server_send_messages(&server);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import websocket
|
||||
from common_test_methods import get_env_config_variable
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
OPCODE_TEXT = 0x1
|
||||
OPCODE_BIN = 0x2
|
||||
OPCODE_PING = 0x9
|
||||
OPCODE_PONG = 0xA
|
||||
CORRECT_ASYNC_DATA = 'Hello client'
|
||||
|
||||
|
||||
class WsClient:
|
||||
def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
|
||||
self.port = port
|
||||
self.ip = ip
|
||||
sslopt = {'ca_certs': ca_file, 'check_hostname': False}
|
||||
self.ws = websocket.WebSocket(sslopt=sslopt)
|
||||
# Set timeout to 10 seconds to avoid connection failure at the time of handshake
|
||||
self.ws.settimeout(10)
|
||||
|
||||
def __enter__(self): # type: ignore
|
||||
self.ws.connect(f'wss://{self.ip}:{self.port}/ws')
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): # type: ignore
|
||||
self.ws.close()
|
||||
|
||||
def read(self) -> Any:
|
||||
return self.ws.recv_data(control_frame=True)
|
||||
|
||||
def write(self, data: str, opcode: int = OPCODE_TEXT) -> Any:
|
||||
if opcode == OPCODE_PING:
|
||||
return self.ws.ping(data)
|
||||
if opcode == OPCODE_PONG:
|
||||
return self.ws.pong(data)
|
||||
return self.ws.send(data)
|
||||
|
||||
|
||||
class wss_client_thread(threading.Thread):
|
||||
def __init__(self, ip, port, ca_file): # type: (str, int, str) -> None
|
||||
threading.Thread.__init__(self)
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.ca_file = ca_file
|
||||
self.start_time = time.time()
|
||||
self.exc = None
|
||||
self.data = 'Espressif'
|
||||
self.async_response = False
|
||||
|
||||
def run(self) -> None:
|
||||
with WsClient(self.ip, self.port, self.ca_file) as ws:
|
||||
while True:
|
||||
try:
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
|
||||
if opcode == OPCODE_PING:
|
||||
ws.write(data=self.data, opcode=OPCODE_PONG)
|
||||
if opcode == OPCODE_TEXT:
|
||||
if data == CORRECT_ASYNC_DATA:
|
||||
self.async_response = True
|
||||
logging.info(f'Thread {self.name} obtained correct async message')
|
||||
# Keep sending pong to update the keepalive in the server
|
||||
if (time.time() - self.start_time) > 20:
|
||||
break
|
||||
except Exception as e:
|
||||
logging.info('Failed to connect to the client and read async data')
|
||||
self.exc = e # type: ignore
|
||||
if self.async_response is not True:
|
||||
self.exc = RuntimeError('Failed to obtain correct async data') # type: ignore
|
||||
|
||||
def join(self, timeout: float | None = 0) -> None:
|
||||
threading.Thread.join(self)
|
||||
if self.exc:
|
||||
raise self.exc
|
||||
|
||||
|
||||
def test_multiple_client_keep_alive_and_async_response(ip, port, ca_file): # type: (str, int, str) -> None
|
||||
threads = []
|
||||
for _ in range(3):
|
||||
try:
|
||||
thread = wss_client_thread(ip, port, ca_file)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
except OSError:
|
||||
logging.info('Error: unable to start thread')
|
||||
# keep delay of 5 seconds between two connections to avoid handshake timeout
|
||||
time.sleep(5)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
|
||||
@pytest.mark.wifi_router
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_protocol_https_wss_server(dut: Dut) -> None:
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut.app.binary_path, 'wss_server.bin')
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
logging.info(f'https_wss_server_bin_size : {bin_size // 1024}KB')
|
||||
|
||||
logging.info('Starting wss_server test app')
|
||||
|
||||
logging.info('Waiting to connect with AP')
|
||||
if dut.app.sdkconfig.get('EXAMPLE_WIFI_SSID_PWD_FROM_STDIN') is True:
|
||||
dut.expect('Please input ssid password:')
|
||||
env_name = 'wifi_router'
|
||||
ap_ssid = get_env_config_variable(env_name, 'ap_ssid')
|
||||
ap_password = get_env_config_variable(env_name, 'ap_password')
|
||||
dut.write(f'{ap_ssid} {ap_password}')
|
||||
# Parse IP address of STA
|
||||
got_port = int(dut.expect(r'Server listening on port (\d+)', timeout=30)[1].decode())
|
||||
got_ip = dut.expect(r'IPv4 address: (\d+\.\d+\.\d+\.\d+)[^\d]', timeout=60)[1].decode()
|
||||
|
||||
logging.info(f'Got IP : {got_ip}')
|
||||
logging.info(f'Got Port : {got_port}')
|
||||
|
||||
ca_file = os.path.join(os.path.dirname(__file__), 'main', 'certs', 'servercert.pem')
|
||||
# Start ws server test
|
||||
with WsClient(got_ip, int(got_port), ca_file) as ws:
|
||||
# Check for echo
|
||||
DATA = 'Espressif'
|
||||
dut.expect('performing session handshake')
|
||||
client_fd = int(dut.expect(r'New client connected (\d+)', timeout=30)[1].decode())
|
||||
ws.write(data=DATA, opcode=OPCODE_TEXT)
|
||||
dut.expect(rf'Received packet with message: {DATA}')
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
if data != DATA:
|
||||
raise RuntimeError('Failed to receive the correct echo response.')
|
||||
logging.info('Correct echo response obtained from the wss server')
|
||||
|
||||
# Test for PING
|
||||
logging.info('Testing for send PING')
|
||||
ws.write(data=DATA, opcode=OPCODE_PING)
|
||||
dut.expect('Got a WS PING frame, Replying PONG')
|
||||
opcode, data = ws.read()
|
||||
data = data.decode('UTF-8')
|
||||
if data != DATA or opcode != OPCODE_PONG:
|
||||
raise RuntimeError('Failed to receive the PONG response')
|
||||
logging.info('Passed the test for PING')
|
||||
|
||||
# Test for keepalive
|
||||
logging.info('Testing for keep alive (approx time = 20s)')
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
opcode, data = ws.read()
|
||||
if opcode == OPCODE_PING:
|
||||
ws.write(data='Espressif', opcode=OPCODE_PONG)
|
||||
logging.info('Received PING, replying PONG (to update the keepalive)')
|
||||
# Keep sending pong to update the keepalive in the server
|
||||
if (time.time() - start_time) > 20:
|
||||
break
|
||||
except Exception:
|
||||
logging.info('Failed the test for keep alive,\nthe client got abruptly disconnected')
|
||||
raise
|
||||
|
||||
# keepalive timeout is 10 seconds so do not respond for (10 + 1) seconds
|
||||
logging.info(
|
||||
'Testing if client is disconnected if it does not respond for 10s '
|
||||
'i.e. keep_alive timeout (approx time = 11s)'
|
||||
)
|
||||
try:
|
||||
dut.expect(f'Client not alive, closing fd {client_fd}', timeout=20)
|
||||
dut.expect(f'Client disconnected {client_fd}')
|
||||
except Exception:
|
||||
logging.info('ENV_ERROR:Failed the test for keep alive,\nthe connection was not closed after timeout')
|
||||
|
||||
time.sleep(11)
|
||||
logging.info('Passed the test for keep alive')
|
||||
|
||||
# Test keep alive and async response for multiple simultaneous client connections
|
||||
logging.info('Testing for multiple simultaneous client connections (approx time = 30s)')
|
||||
test_multiple_client_keep_alive_and_async_response(got_ip, int(got_port), ca_file)
|
||||
logging.info('Passed the test for multiple simultaneous client connections')
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_EXAMPLE_WIFI_SSID_PWD_FROM_STDIN=y
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
Reference in New Issue
Block a user