Compare commits
No commits in common. "67b97b33960d1c0b1b1e8111b797fd71fa5c119b" and "4600f384dd4142ad8320e9e672748fae5d516aa1" have entirely different histories.
67b97b3396
...
4600f384dd
107
README.md
107
README.md
|
@ -20,7 +20,7 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
|
||||||
- [x] ESP32
|
- [x] ESP32
|
||||||
- [x] ESP32C3
|
- [x] ESP32C3
|
||||||
- [x] ESP32S3
|
- [x] ESP32S3
|
||||||
|
|
||||||
2. Debug Communication Mode
|
2. Debug Communication Mode
|
||||||
- [x] SWD
|
- [x] SWD
|
||||||
- [x] JTAG
|
- [x] JTAG
|
||||||
|
@ -35,8 +35,6 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
|
||||||
5. More..
|
5. More..
|
||||||
- [x] SWD protocol based on SPI acceleration (Up to 40MHz)
|
- [x] SWD protocol based on SPI acceleration (Up to 40MHz)
|
||||||
- [x] Support for [elaphureLink](https://github.com/windowsair/elaphureLink), fast Keil debug without drivers
|
- [x] Support for [elaphureLink](https://github.com/windowsair/elaphureLink), fast Keil debug without drivers
|
||||||
- [x] Support for [elaphure-dap.js](https://github.com/windowsair/elaphure-dap.js), online ARM Cortex-M firmware flash
|
|
||||||
- [x] Support for OpenOCD/pyOCD
|
|
||||||
- [x] ...
|
- [x] ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,53 +43,14 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
|
||||||
|
|
||||||
### WIFI
|
### WIFI
|
||||||
|
|
||||||
The default connected WIFI SSID is `无线DAP` , password `12345678`
|
|
||||||
|
|
||||||
Support for specifying multiple possible WAP. It can be added here: [wifi_configuration.h](main/wifi_configuration.h)
|
|
||||||
|
|
||||||
You can also specify your IP in the above file (We recommend using the static address binding feature of the router).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
There is built-in ipv4 only mDNS server. You can access the device using `dap.local`.
|
There is built-in ipv4 only mDNS server. You can access the device using `dap.local`.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Debugger
|
### Debugger
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>ESP32</summary>
|
|
||||||
|
|
||||||
| SWD | |
|
|
||||||
|----------------|--------|
|
|
||||||
| SWCLK | GPIO14 |
|
|
||||||
| SWDIO | GPIO13 |
|
|
||||||
| TVCC | 3V3 |
|
|
||||||
| GND | GND |
|
|
||||||
|
|
||||||
|
|
||||||
--------------
|
|
||||||
|
|
||||||
|
|
||||||
| JTAG | |
|
|
||||||
|--------------------|---------|
|
|
||||||
| TCK | GPIO14 |
|
|
||||||
| TMS | GPIO13 |
|
|
||||||
| TDI | GPIO18 |
|
|
||||||
| TDO | GPIO19 |
|
|
||||||
| nTRST \(optional\) | GPIO25 |
|
|
||||||
| nRESET | GPIO26 |
|
|
||||||
| TVCC | 3V3 |
|
|
||||||
| GND | GND |
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>ESP32C3</summary>
|
<summary>ESP32C3</summary>
|
||||||
|
|
||||||
|
@ -121,6 +80,32 @@ There is built-in ipv4 only mDNS server. You can access the device using `dap.lo
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>ESP32</summary>
|
||||||
|
|
||||||
|
| SWD | |
|
||||||
|
|----------------|--------|
|
||||||
|
| SWCLK | GPIO14 |
|
||||||
|
| SWDIO | GPIO13 |
|
||||||
|
| TVCC | 3V3 |
|
||||||
|
| GND | GND |
|
||||||
|
|
||||||
|
--------------
|
||||||
|
|
||||||
|
| JTAG | |
|
||||||
|
|--------------------|---------|
|
||||||
|
| TCK | GPIO14 |
|
||||||
|
| TMS | GPIO13 |
|
||||||
|
| TDI | GPIO18 |
|
||||||
|
| TDO | GPIO19 |
|
||||||
|
| nTRST \(optional\) | GPIO25 |
|
||||||
|
| nRESET | GPIO26 |
|
||||||
|
| TVCC | 3V3 |
|
||||||
|
| GND | GND |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>ESP32S3</summary>
|
<summary>ESP32S3</summary>
|
||||||
|
|
||||||
|
@ -149,6 +134,7 @@ There is built-in ipv4 only mDNS server. You can access the device using `dap.lo
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,12 +247,18 @@ When you select max clock, we will take the following actions:
|
||||||
|
|
||||||
This project was originally designed to run on Keil, but now you can also perform firmware flash on OpenOCD.
|
This project was originally designed to run on Keil, but now you can also perform firmware flash on OpenOCD.
|
||||||
|
|
||||||
|
Note that if you want to use a 40MHz SPI acceleration, you need to specify the speed after the target device is connected, otherwise it will fail with the beginning.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Run before approaching the flash command
|
||||||
|
> adapter speed 10000
|
||||||
|
|
||||||
> halt
|
> halt
|
||||||
> flash write_image [erase] [unlock] filename [offset] [type]
|
> flash write_image [erase] [unlock] filename [offset] [type]
|
||||||
```
|
```
|
||||||
|
|
||||||
> pyOCD is now supported.
|
> Keil's timing handling is somewhat different from OpenOCD's. For example, OpenOCD lacks the SWD line reset sequence before reading the `IDCODE` registers.
|
||||||
|
|
||||||
|
|
||||||
### Uart TCP Bridge
|
### Uart TCP Bridge
|
||||||
|
|
||||||
|
@ -275,6 +267,8 @@ TCP server on PORT 1234.
|
||||||
UART default baud: 74880
|
UART default baud: 74880
|
||||||
|
|
||||||
This feature provides a bridge between TCP and Uart:
|
This feature provides a bridge between TCP and Uart:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Send data -> TCP -> Uart TX -> external devices
|
Send data -> TCP -> Uart TX -> external devices
|
||||||
|
|
||||||
|
@ -319,30 +313,6 @@ For example, sending the ASCII text `115200` will switch the baud rate to 115200
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
2020.12.1
|
|
||||||
|
|
||||||
TCP transmission speed needs to be further improved.
|
|
||||||
|
|
||||||
2020.11.11
|
|
||||||
|
|
||||||
Winusb is now available, but it is very slow.
|
|
||||||
|
|
||||||
|
|
||||||
2020.2.4
|
|
||||||
|
|
||||||
Due to the limitation of USB-HID (I'm not sure if this is a problem with USBIP or Windows), now each URB packet can only reach 255 bytes (About 1MBps bandwidth), which has not reached the upper limit of ESP8266 transmission bandwidth.
|
|
||||||
|
|
||||||
I now have an idea to construct a Man-in-the-middle between the two to forward traffic, thereby increasing the bandwidth of each transmission.
|
|
||||||
|
|
||||||
2020.1.31
|
|
||||||
|
|
||||||
At present, the adaptation to WCID, WinUSB, etc. has all been completed. However, when transmitting data on the endpoint, we received an error message from USBIP. This is most likely a problem with the USBIP project itself.
|
|
||||||
|
|
||||||
Due to the completeness of the USBIP protocol document, we have not yet understood its role in the Bulk transmission process, which may also lead to errors in subsequent processes.
|
|
||||||
|
|
||||||
We will continue to try to make it work on USB HID. Once the USBIP problem is solved, we will immediately transfer it to work on WinUSB
|
|
||||||
|
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Credit
|
## Credit
|
||||||
|
@ -364,5 +334,4 @@ Credits to the following project, people and organizations:
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT LICENSE](LICENSE)
|
[Apache2.0 LICENSE](LICENSE)
|
||||||
|
|
||||||
|
|
68
README_CN.md
68
README_CN.md
|
@ -29,16 +29,14 @@
|
||||||
|
|
||||||
5. 其它
|
5. 其它
|
||||||
- [x] 通过SPI接口加速的SWD协议(最高可达40MHz)
|
- [x] 通过SPI接口加速的SWD协议(最高可达40MHz)
|
||||||
- [x] 支持 [elaphureLink](https://github.com/windowsair/elaphureLink),无需驱动的快速Keil 调试
|
- [x] 支持[elaphureLink](https://github.com/windowsair/elaphureLink),无需驱动的快速Keil调试
|
||||||
- [x] 支持 [elaphure-dap.js](https://github.com/windowsair/elaphure-dap.js),网页端的 ARM Cortex-M 设备固件烧录调试
|
|
||||||
- [x] 支持 OpenOCD/pyOCD
|
|
||||||
- [x] ...
|
- [x] ...
|
||||||
|
|
||||||
## 连接你的开发板
|
## 连接你的开发板
|
||||||
|
|
||||||
### WIFI连接
|
### WIFI连接
|
||||||
|
|
||||||
固件默认的WIFI SSID是`无线DAP`或者`OTA`,密码是`12345678`。
|
|
||||||
|
|
||||||
固件中已经内置了一个mDNS服务。你可以通过`dap.local`的地址访问到设备。
|
固件中已经内置了一个mDNS服务。你可以通过`dap.local`的地址访问到设备。
|
||||||
|
|
||||||
|
@ -47,32 +45,6 @@
|
||||||
|
|
||||||
### 调试接口连接
|
### 调试接口连接
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>ESP32</summary>
|
|
||||||
|
|
||||||
| SWD | |
|
|
||||||
|----------------|--------|
|
|
||||||
| SWCLK | GPIO14 |
|
|
||||||
| SWDIO | GPIO13 |
|
|
||||||
| TVCC | 3V3 |
|
|
||||||
| GND | GND |
|
|
||||||
|
|
||||||
--------------
|
|
||||||
|
|
||||||
| JTAG | |
|
|
||||||
|--------------------|---------|
|
|
||||||
| TCK | GPIO14 |
|
|
||||||
| TMS | GPIO13 |
|
|
||||||
| TDI | GPIO18 |
|
|
||||||
| TDO | GPIO19 |
|
|
||||||
| nTRST \(optional\) | GPIO25 |
|
|
||||||
| nRESET | GPIO26 |
|
|
||||||
| TVCC | 3V3 |
|
|
||||||
| GND | GND |
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>ESP32C3</summary>
|
<summary>ESP32C3</summary>
|
||||||
|
|
||||||
|
@ -97,6 +69,31 @@
|
||||||
| GND | GND |
|
| GND | GND |
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>ESP32</summary>
|
||||||
|
|
||||||
|
| SWD | |
|
||||||
|
|----------------|--------|
|
||||||
|
| SWCLK | GPIO14 |
|
||||||
|
| SWDIO | GPIO13 |
|
||||||
|
| TVCC | 3V3 |
|
||||||
|
| GND | GND |
|
||||||
|
|
||||||
|
--------------
|
||||||
|
|
||||||
|
| JTAG | |
|
||||||
|
|--------------------|---------|
|
||||||
|
| TCK | GPIO14 |
|
||||||
|
| TMS | GPIO13 |
|
||||||
|
| TDI | GPIO18 |
|
||||||
|
| TDO | GPIO19 |
|
||||||
|
| nTRST \(optional\) | GPIO25 |
|
||||||
|
| nRESET | GPIO26 |
|
||||||
|
| TVCC | 3V3 |
|
||||||
|
| GND | GND |
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -161,6 +158,9 @@ idf.py -p /dev/ttyS5 flash
|
||||||
|
|
||||||
> 位于项目根目录的`idf.py`脚本仅适用于较老的ESP8266设备,请不要在ESP32设备上使用。
|
> 位于项目根目录的`idf.py`脚本仅适用于较老的ESP8266设备,请不要在ESP32设备上使用。
|
||||||
|
|
||||||
|
|
||||||
|
> 我们还提供了预编译固件用于快速评估。详见 [Releases](https://github.com/windowsair/wireless-esp8266-dap/releases)
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
@ -228,13 +228,17 @@ idf.py -p /dev/ttyS5 flash
|
||||||
### 对于OpenOCD用户
|
### 对于OpenOCD用户
|
||||||
|
|
||||||
这个项目最初是为在Keil上运行而设计的,但现在你也可以在OpenOCD上通过它来烧录程序。
|
这个项目最初是为在Keil上运行而设计的,但现在你也可以在OpenOCD上通过它来烧录程序。
|
||||||
|
注意,如果你想使用40MHz的SPI加速器,你需要在连接目标设备后指定速度,否则会在开始时失败。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 在使用flash指令前需要先运行:
|
||||||
|
> adapter speed 10000
|
||||||
|
|
||||||
> halt
|
> halt
|
||||||
> flash write_image [erase] [unlock] filename [offset] [type]
|
> flash write_image [erase] [unlock] filename [offset] [type]
|
||||||
```
|
```
|
||||||
|
|
||||||
> 现已支持 pyOCD
|
> Keil的操作时序与OpenOCD的有些不同。例如,OpenOCD在读取 "IDCODE "寄存器之前缺少SWD线复位序列。
|
||||||
|
|
||||||
### TCP转发的串口
|
### TCP转发的串口
|
||||||
|
|
||||||
|
@ -274,4 +278,4 @@ TCP端口:1234
|
||||||
|
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
[MIT 许可证](LICENSE)
|
[Apache 2.0 许可证](LICENSE)
|
||||||
|
|
|
@ -3,5 +3,5 @@ file(GLOB SOURCES *.c)
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCES}
|
SRCS ${SOURCES}
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
PRIV_REQUIRES DAP USBIP esp_ringbuf mbedtls
|
PRIV_REQUIRES DAP USBIP esp_ringbuf
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 windowsair <msdn_02 at sina.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#ifndef _CORSACOTA_H_
|
|
||||||
#define _CORSACOTA_H_
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef signed int co_err_t;
|
|
||||||
|
|
||||||
#define CO_OK 0
|
|
||||||
#define CO_FAIL -1
|
|
||||||
#define CO_ERROR_IO_PENDING -2
|
|
||||||
#define CO_ERROR_NO_MEM 0x101
|
|
||||||
#define CO_ERROR_INVALID_ARG 0x102
|
|
||||||
#define CO_ERROR_INVALID_SIZE 0x104
|
|
||||||
#define CO_ERROR_INVALID_OTA_PTN -3
|
|
||||||
|
|
||||||
#define CO_RES_SUCCESS 0
|
|
||||||
#define CO_RES_SYSTEM_ERROR 1
|
|
||||||
#define CO_RES_INVALID_ARG 2
|
|
||||||
#define CO_RES_INVALID_SIZE 3
|
|
||||||
#define CO_RES_INVALID_STATUS 4
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief corsacOTA instance handle. Only one instance is allowed.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typedef void *co_handle_t;
|
|
||||||
|
|
||||||
typedef struct co_config {
|
|
||||||
char *thread_name; // corsacOTA thread name
|
|
||||||
int stack_size; // corsacOTA thread stack size
|
|
||||||
int thread_prio; // corsacOTA thread priority
|
|
||||||
|
|
||||||
int listen_port; // corsacOTA server listen port
|
|
||||||
int max_listen_num; // Maximum number of connections. In fact, after the handshake is complete, there is only one connection to provide services.
|
|
||||||
|
|
||||||
int wait_timeout_sec; // Timeout (in seconds)
|
|
||||||
int wait_timeout_usec; // Timeout (in microseconds)
|
|
||||||
|
|
||||||
} co_config_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Start the corsacOTA server
|
|
||||||
*
|
|
||||||
* @param config Configuration for new instance of the server
|
|
||||||
* @return
|
|
||||||
* - ESP_OK : Server Init successfully
|
|
||||||
* - ESP_ERR_INVALID_ARG : Null argument
|
|
||||||
* - ESP_ERR_NO_MEM : Failed to allocate memory for instance
|
|
||||||
*/
|
|
||||||
int corsacOTA_init(co_handle_t *handle, co_config_t *config);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -28,7 +28,7 @@ int kSock = -1;
|
||||||
|
|
||||||
void tcp_server_task(void *pvParameters)
|
void tcp_server_task(void *pvParameters)
|
||||||
{
|
{
|
||||||
uint8_t tcp_rx_buffer[1500] = {0};
|
uint8_t tcp_rx_buffer[1500];
|
||||||
char addr_str[128];
|
char addr_str[128];
|
||||||
enum usbip_server_state_t usbip_state = WAIT_DEVLIST;
|
enum usbip_server_state_t usbip_state = WAIT_DEVLIST;
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
|
@ -41,31 +41,31 @@ void tcp_server_task(void *pvParameters)
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef CONFIG_EXAMPLE_IPV4
|
#ifdef CONFIG_EXAMPLE_IPV6
|
||||||
struct sockaddr_in destAddr;
|
struct sockaddr_in6 destAddr;
|
||||||
destAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
destAddr.sin_family = AF_INET;
|
|
||||||
destAddr.sin_port = htons(DAP_PROXY_PORT);
|
|
||||||
addr_family = AF_INET;
|
|
||||||
ip_protocol = IPPROTO_IP;
|
|
||||||
inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
|
|
||||||
#else // IPV6
|
|
||||||
struct sockaddr_in6 destAddr;
|
|
||||||
bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un));
|
bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un));
|
||||||
destAddr.sin6_family = AF_INET6;
|
destAddr.sin6_family = AF_INET6;
|
||||||
destAddr.sin6_port = htons(DAP_PROXY_PORT);
|
destAddr.sin6_port = htons(DAP_PROXY_PORT);
|
||||||
addr_family = AF_INET6;
|
addr_family = AF_INET6;
|
||||||
ip_protocol = IPPROTO_IPV6;
|
ip_protocol = IPPROTO_IPV6;
|
||||||
inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
|
inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
|
||||||
|
#else // IPV6
|
||||||
|
struct sockaddr_in destAddr;
|
||||||
|
destAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
destAddr.sin_family = AF_INET;
|
||||||
|
destAddr.sin_port = htons(DAP_PROXY_PORT);
|
||||||
|
addr_family = AF_INET;
|
||||||
|
ip_protocol = IPPROTO_IP;
|
||||||
|
inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
|
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
|
||||||
if (listen_sock < 0)
|
if (listen_sock < 0)
|
||||||
{
|
{
|
||||||
printf("Unable to create socket: errno %d\r\n", errno);
|
printf("Unable to create socket: errno %d\r\n", errno);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("Socket created\r\n");
|
printf("Socket created\r\n");
|
||||||
|
|
||||||
setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
|
setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
|
||||||
setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
|
setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
|
||||||
|
@ -73,18 +73,18 @@ void tcp_server_task(void *pvParameters)
|
||||||
int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr));
|
int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr));
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
{
|
{
|
||||||
printf("Socket unable to bind: errno %d\r\n", errno);
|
printf("Socket unable to bind: errno %d\r\n", errno);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("Socket binded\r\n");
|
printf("Socket binded\r\n");
|
||||||
|
|
||||||
err = listen(listen_sock, 1);
|
err = listen(listen_sock, 1);
|
||||||
if (err != 0)
|
if (err != 0)
|
||||||
{
|
{
|
||||||
printf("Error occured during listen: errno %d\r\n", errno);
|
printf("Error occured during listen: errno %d\r\n", errno);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
printf("Socket listening\r\n");
|
printf("Socket listening\r\n");
|
||||||
|
|
||||||
#ifdef CONFIG_EXAMPLE_IPV6
|
#ifdef CONFIG_EXAMPLE_IPV6
|
||||||
struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6
|
struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6
|
||||||
|
@ -97,12 +97,12 @@ void tcp_server_task(void *pvParameters)
|
||||||
kSock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen);
|
kSock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen);
|
||||||
if (kSock < 0)
|
if (kSock < 0)
|
||||||
{
|
{
|
||||||
printf("Unable to accept connection: errno %d\r\n", errno);
|
printf("Unable to accept connection: errno %d\r\n", errno);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setsockopt(kSock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
|
setsockopt(kSock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
|
||||||
setsockopt(kSock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
|
setsockopt(kSock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
|
||||||
printf("Socket accepted\r\n");
|
printf("Socket accepted\r\n");
|
||||||
|
|
||||||
// Read header
|
// Read header
|
||||||
sz = 4;
|
sz = 4;
|
||||||
|
@ -127,10 +127,6 @@ void tcp_server_task(void *pvParameters)
|
||||||
else
|
else
|
||||||
usbip_state = WAIT_IMPORT;
|
usbip_state = WAIT_IMPORT;
|
||||||
usbip_worker(tcp_rx_buffer, sizeof(tcp_rx_buffer), &usbip_state);
|
usbip_worker(tcp_rx_buffer, sizeof(tcp_rx_buffer), &usbip_state);
|
||||||
} else if (header == 0x47455420) { // string "GET "
|
|
||||||
#ifdef CONFIG_USE_WEBSOCKET_DAP
|
|
||||||
websocket_worker(kSock, tcp_rx_buffer, sizeof(tcp_rx_buffer));
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
printf("Unknown protocol\n");
|
printf("Unknown protocol\n");
|
||||||
}
|
}
|
||||||
|
@ -138,7 +134,7 @@ void tcp_server_task(void *pvParameters)
|
||||||
cleanup:
|
cleanup:
|
||||||
if (kSock != -1)
|
if (kSock != -1)
|
||||||
{
|
{
|
||||||
printf("Shutting down socket and restarting...\r\n");
|
printf("Shutting down socket and restarting...\r\n");
|
||||||
//shutdown(kSock, 0);
|
//shutdown(kSock, 0);
|
||||||
close(kSock);
|
close(kSock);
|
||||||
|
|
||||||
|
@ -147,7 +143,7 @@ cleanup:
|
||||||
|
|
||||||
kRestartDAPHandle = RESET_HANDLE;
|
kRestartDAPHandle = RESET_HANDLE;
|
||||||
if (kDAPTaskHandle)
|
if (kDAPTaskHandle)
|
||||||
xTaskNotifyGive(kDAPTaskHandle);
|
xTaskNotifyGive(kDAPTaskHandle);
|
||||||
|
|
||||||
//shutdown(listen_sock, 0);
|
//shutdown(listen_sock, 0);
|
||||||
//close(listen_sock);
|
//close(listen_sock);
|
||||||
|
|
|
@ -1,946 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2021 windowsair <msdn_02 at sina.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/param.h>
|
|
||||||
|
|
||||||
// share header file
|
|
||||||
#include "corsacOTA.h"
|
|
||||||
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
|
|
||||||
#include "mbedtls/base64.h"
|
|
||||||
#include "mbedtls/sha1.h"
|
|
||||||
|
|
||||||
|
|
||||||
#include "esp_system.h"
|
|
||||||
|
|
||||||
#include "lwip/err.h"
|
|
||||||
#include "lwip/sockets.h"
|
|
||||||
#include "lwip/sys.h"
|
|
||||||
#include <lwip/netdb.h>
|
|
||||||
|
|
||||||
#include "sdkconfig.h"
|
|
||||||
|
|
||||||
static const char *CO_TAG = "corsacOTA";
|
|
||||||
|
|
||||||
#define CONFIG_CO_SOCKET_BUFFER_SIZE 1500
|
|
||||||
#define CONFIG_CO_WS_TEXT_BUFFER_SIZE 100
|
|
||||||
|
|
||||||
#define LOG_FMT(x) "%s: " x, __func__
|
|
||||||
|
|
||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
||||||
|
|
||||||
#define CO_NO_RETURN __attribute__((noreturn))
|
|
||||||
#define CO_INLINE __attribute__((always_inline))
|
|
||||||
|
|
||||||
#define CO_TEST_MODE 0
|
|
||||||
|
|
||||||
#if (CO_TEST_MODE == 1)
|
|
||||||
#warning corsacOTA test mode is in use
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern void free_dap_ringbuf();
|
|
||||||
extern uint32_t DAP_ExecuteCommand(const uint8_t *request, uint8_t *response);
|
|
||||||
|
|
||||||
static void co_websocket_process_dap(uint8_t *data, size_t len);
|
|
||||||
|
|
||||||
uint8_t* ws_process_buffer = NULL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief corsacOTA websocket control block
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typedef struct co_websocket_cb {
|
|
||||||
uint8_t FIN;
|
|
||||||
uint8_t OPCODE;
|
|
||||||
|
|
||||||
uint8_t MASK;
|
|
||||||
size_t payload_len; // it is used not only for the 7-bit payload len,
|
|
||||||
// but also for the total length of the payload after the extended payload length is included.
|
|
||||||
|
|
||||||
size_t payload_read_len; // the number of payload bytes already read
|
|
||||||
|
|
||||||
union {
|
|
||||||
uint32_t val;
|
|
||||||
uint8_t data[4];
|
|
||||||
} mask;
|
|
||||||
|
|
||||||
bool skip_frame; // skip too long text frames
|
|
||||||
} co_websocket_cb_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief corsacOTA socket control block
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typedef struct co_socket_cb {
|
|
||||||
int fd; // The file descriptor for this socket
|
|
||||||
enum co_socket_status {
|
|
||||||
CO_SOCKET_ACCEPT = 0,
|
|
||||||
CO_SOCKET_HANDSHAKE, // not handshake, or in progress
|
|
||||||
CO_SOCKET_WEBSOCKET_HEADER, // already handshake, now reading the header of websocket frame
|
|
||||||
CO_SOCKET_WEBSOCKET_EXTEND_LENGTH, // reading the extended length of websocket header
|
|
||||||
CO_SOCKET_WEBSOCKET_MASK, // reading the mask part of websocket header
|
|
||||||
CO_SOCKET_WEBSOCKET_PAYLOAD, // reading the payload of websocket frame
|
|
||||||
CO_SOCKET_CLOSING // waiting to close
|
|
||||||
} status;
|
|
||||||
|
|
||||||
char *buf; // data from raw socket
|
|
||||||
size_t remaining_len; // the number of available bytes remaining in buf
|
|
||||||
size_t read_len; // the number of bytes that have been processed
|
|
||||||
|
|
||||||
co_websocket_cb_t wcb; // websocket control block
|
|
||||||
|
|
||||||
} co_socket_cb_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief corsacOTA OTA control block
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typedef struct co_ota_cb {
|
|
||||||
enum co_ota_status {
|
|
||||||
CO_OTA_INIT = 0,
|
|
||||||
CO_OTA_LOAD,
|
|
||||||
CO_OTA_DONE,
|
|
||||||
CO_OTA_STOP,
|
|
||||||
CO_OTA_ERROR,
|
|
||||||
CO_OTA_FATAL_ERROR,
|
|
||||||
} status;
|
|
||||||
int32_t error_code; //// TODO: ?
|
|
||||||
|
|
||||||
int32_t total_size; // Total firmware size
|
|
||||||
int32_t offset; // Current processed size
|
|
||||||
int32_t chunk_size; // The response will be made every time the chunk size is reached
|
|
||||||
int32_t last_index_offset; // The offset recorded in the last response
|
|
||||||
|
|
||||||
} co_ota_cb_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief corsacOTA http control block
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
typedef struct co_cb {
|
|
||||||
int listen_fd; // server listener FD
|
|
||||||
int websocket_fd; // only one websocket is allowed.
|
|
||||||
uint8_t *recv_data; // recv buffer at websocket stage (text mode)
|
|
||||||
int recv_data_offset; // (text mode)
|
|
||||||
int max_listen_num; // maxium number of connections. In fact, after the handshake is complete, there is only one connection to provide services
|
|
||||||
|
|
||||||
int wait_timeout_sec; // timeout (in seconds)
|
|
||||||
int wait_timeout_usec; // timeout (in microseconds)
|
|
||||||
|
|
||||||
co_socket_cb_t **socket_list; // socket control block list
|
|
||||||
co_socket_cb_t *websocket; // the only valid socket in the list
|
|
||||||
|
|
||||||
int accept_num; // current number of established connections
|
|
||||||
|
|
||||||
int closing_num; // current number of closing socket
|
|
||||||
|
|
||||||
co_ota_cb_t ota; // ota control block
|
|
||||||
|
|
||||||
} co_cb_t;
|
|
||||||
|
|
||||||
static co_cb_t *global_cb = NULL;
|
|
||||||
|
|
||||||
/* RFC 6455: The WebSocket Protocol
|
|
||||||
|
|
||||||
0 1 2 3
|
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
||||||
+-+-+-+-+-------+-+-------------+-------------------------------+
|
|
||||||
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|
|
||||||
|I|S|S|S| (4) |A| (7) | (16/64) |
|
|
||||||
|N|V|V|V| |S| | (if payload len==126/127) |
|
|
||||||
| |1|2|3| |K| | |
|
|
||||||
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
|
||||||
| Extended payload length continued, if payload len == 127 |
|
|
||||||
+ - - - - - - - - - - - - - - - +-------------------------------+
|
|
||||||
| |Masking-key, if MASK set to 1 |
|
|
||||||
+-------------------------------+-------------------------------+
|
|
||||||
| Masking-key (continued) | Payload Data |
|
|
||||||
+-------------------------------- - - - - - - - - - - - - - - - +
|
|
||||||
: Payload Data continued ... :
|
|
||||||
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
|
||||||
| Payload Data continued ... |
|
|
||||||
+---------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define WS_FIN 0x80
|
|
||||||
#define WS_RSV1 0x40
|
|
||||||
#define WS_RSV2 0x20
|
|
||||||
#define WS_RSV3 0x10
|
|
||||||
#define WS_OPCODE_CONTINUTAION 0x00
|
|
||||||
#define WS_OPCODE_TEXT 0x01
|
|
||||||
#define WS_OPCODE_BINARY 0x02
|
|
||||||
#define WS_OPCODE_CLOSE 0x08
|
|
||||||
#define WS_OPCODE_PING 0x09
|
|
||||||
#define WS_OPCODE_PONG 0x0A
|
|
||||||
|
|
||||||
#define WS_MASK 0x80
|
|
||||||
|
|
||||||
static inline int co_websocket_get_res_payload_offset(int payload_len) {
|
|
||||||
// promise: payload_len <= 65535
|
|
||||||
return 2 + (payload_len >= 126 ? 2 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static co_err_t co_websocket_process_header(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
uint8_t opcode, fin, mask;
|
|
||||||
uint64_t payload_len;
|
|
||||||
uint8_t *data;
|
|
||||||
|
|
||||||
data = (uint8_t *)scb->buf;
|
|
||||||
|
|
||||||
if (scb->status == CO_SOCKET_WEBSOCKET_HEADER) {
|
|
||||||
if (scb->remaining_len < 2) {
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check RSV
|
|
||||||
if (data[0] & 0b1110000) {
|
|
||||||
return CO_FAIL; // no extension defining RSV
|
|
||||||
}
|
|
||||||
|
|
||||||
// first byte
|
|
||||||
fin = (data[0] & WS_FIN) == WS_FIN;
|
|
||||||
opcode = data[0] & 0b1111;
|
|
||||||
// second byte
|
|
||||||
mask = (data[1] & WS_MASK) == WS_MASK;
|
|
||||||
payload_len = data[1] & 0x7F;
|
|
||||||
|
|
||||||
switch (opcode) {
|
|
||||||
case WS_OPCODE_CONTINUTAION:
|
|
||||||
// nothing to do
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_TEXT:
|
|
||||||
case WS_OPCODE_BINARY:
|
|
||||||
scb->wcb.OPCODE = opcode;
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_PING:
|
|
||||||
case WS_OPCODE_PONG:
|
|
||||||
scb->wcb.OPCODE = opcode;
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_CLOSE:
|
|
||||||
scb->wcb.OPCODE = opcode;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return CO_FAIL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
scb->wcb.FIN = fin;
|
|
||||||
scb->wcb.MASK = mask;
|
|
||||||
scb->wcb.payload_len = payload_len;
|
|
||||||
|
|
||||||
// extended payload length should be read
|
|
||||||
if (payload_len == 126 || payload_len == 127) {
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_EXTEND_LENGTH;
|
|
||||||
} else if (mask == 1) {
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
scb->read_len = 2; // first 2 byte header
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scb->status == CO_SOCKET_WEBSOCKET_EXTEND_LENGTH) {
|
|
||||||
if (scb->wcb.payload_len == 126) {
|
|
||||||
if (scb->remaining_len < scb->read_len + 2) { // 2 byte extended length
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload_len = data[2] << 8 | data[3]; // 0 + scb->read_len == 2
|
|
||||||
|
|
||||||
scb->read_len += 2;
|
|
||||||
} else if (scb->wcb.payload_len == 127) { // 8 byte extended length
|
|
||||||
if (scb->remaining_len < scb->read_len + 8) {
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
payload_len = ((uint64_t)(data[9]) << 0);
|
|
||||||
payload_len |= ((uint64_t)(data[8]) << 8);
|
|
||||||
payload_len |= ((uint64_t)(data[7]) << 16);
|
|
||||||
payload_len |= ((uint64_t)(data[6]) << 24);
|
|
||||||
payload_len |= ((uint64_t)(data[5]) << 32);
|
|
||||||
payload_len |= ((uint64_t)(data[4]) << 40);
|
|
||||||
payload_len |= ((uint64_t)(data[3]) << 48);
|
|
||||||
payload_len |= ((uint64_t)(data[2]) << 56);
|
|
||||||
|
|
||||||
// most significant bit MUST be 0
|
|
||||||
if (((payload_len >> 63) & 0b1) == 0x1) {
|
|
||||||
ESP_LOGE(CO_TAG, "wrong payload length");
|
|
||||||
return CO_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
scb->read_len += 8;
|
|
||||||
} else {
|
|
||||||
payload_len = scb->wcb.payload_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
scb->wcb.payload_len = payload_len;
|
|
||||||
|
|
||||||
scb->status = scb->wcb.MASK == 1 ? CO_SOCKET_WEBSOCKET_MASK : CO_SOCKET_WEBSOCKET_PAYLOAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scb->status == CO_SOCKET_WEBSOCKET_MASK) {
|
|
||||||
if (scb->remaining_len < scb->read_len + 4) { // 4 byte mask
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&scb->wcb.mask.data[0], &data[scb->read_len], 4);
|
|
||||||
scb->read_len += 4;
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_PAYLOAD;
|
|
||||||
} else {
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_PAYLOAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We promise that the length of the payload should not exceed 65535
|
|
||||||
static co_err_t co_websocket_send_frame(void *frame_buffer, size_t payload_len, int frame_type) {
|
|
||||||
int sz;
|
|
||||||
uint16_t payload_length;
|
|
||||||
uint8_t *p;
|
|
||||||
|
|
||||||
payload_length = payload_len;
|
|
||||||
sz = co_websocket_get_res_payload_offset(payload_len) + payload_len;
|
|
||||||
|
|
||||||
p = frame_buffer;
|
|
||||||
// 2 bytes
|
|
||||||
*p++ = WS_FIN | frame_type; // frame_type
|
|
||||||
*p++ = (payload_length >= 126 ? 126 : payload_length);
|
|
||||||
|
|
||||||
// extended length
|
|
||||||
if (payload_length >= 126) {
|
|
||||||
payload_length = htons(payload_length);
|
|
||||||
memcpy(p, &payload_length, 2);
|
|
||||||
p += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no mask
|
|
||||||
|
|
||||||
send(global_cb->websocket->fd, frame_buffer, sz, 0);
|
|
||||||
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new frame buffer, construct text and send frame.
|
|
||||||
static co_err_t co_websocket_send_msg_with_code(int code, const char *msg) {
|
|
||||||
char *buffer;
|
|
||||||
int len, ret;
|
|
||||||
int offset;
|
|
||||||
|
|
||||||
len = strlen(msg);
|
|
||||||
offset = co_websocket_get_res_payload_offset(len);
|
|
||||||
buffer = malloc(offset + len + 25);
|
|
||||||
if (buffer == NULL) {
|
|
||||||
ret = CO_ERROR_NO_MEM;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code == CO_RES_SUCCESS) {
|
|
||||||
ret = snprintf(buffer + offset, len + 24, "code=%d&data=\"%s\"", code, msg);
|
|
||||||
} else {
|
|
||||||
ret = snprintf(buffer + offset, len + 24, "code=%d&data=\"msg=%s\"", code, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
ESP_LOGE(CO_TAG, "invalid arg");
|
|
||||||
ret = CO_ERROR_INVALID_ARG;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = co_websocket_send_frame(buffer, ret, WS_OPCODE_TEXT);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
free(buffer);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if (CO_TEST_MODE == 1)
|
|
||||||
// use for test
|
|
||||||
static co_err_t co_websocket_send_echo(void *data, size_t len, int frame_type) {
|
|
||||||
char *buffer;
|
|
||||||
int ret;
|
|
||||||
int offset;
|
|
||||||
|
|
||||||
offset = co_websocket_get_res_payload_offset(len);
|
|
||||||
buffer = malloc(offset + len);
|
|
||||||
if (buffer == NULL) {
|
|
||||||
ret = CO_ERROR_NO_MEM;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
memcpy(buffer + offset, data, len);
|
|
||||||
|
|
||||||
ret = co_websocket_send_frame(buffer, len, frame_type);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
free(buffer);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
#endif // (CO_TEST_MODE == 1)
|
|
||||||
|
|
||||||
static void co_websocket_process_binary(uint8_t *data, size_t len) {
|
|
||||||
co_websocket_process_dap(data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void co_websocket_process_text(uint8_t *data, size_t len) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// send pong response
|
|
||||||
// TODO: too long ping frame
|
|
||||||
static void co_websocket_process_ping(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
int len;
|
|
||||||
|
|
||||||
// control frame max payload length: 125 -> 0 byte extended length
|
|
||||||
len = 2 + scb->wcb.payload_len + (scb->wcb.MASK ? 4 : 0);
|
|
||||||
|
|
||||||
scb->buf[0] = WS_FIN | WS_OPCODE_PONG;
|
|
||||||
|
|
||||||
send(scb->fd, scb->buf, len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// close handshake
|
|
||||||
// TODO: array
|
|
||||||
static void co_websocket_process_close(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
uint8_t buf[4];
|
|
||||||
uint8_t *p = buf;
|
|
||||||
|
|
||||||
*p++ = WS_FIN | WS_OPCODE_CLOSE;
|
|
||||||
*p++ = 0x02; // 2 byte status code
|
|
||||||
// normal closure
|
|
||||||
*p++ = 0x03;
|
|
||||||
*p = 0xe8;
|
|
||||||
|
|
||||||
send(scb->fd, buf, 4, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline CO_INLINE uint32_t co_rotr32(uint32_t n, unsigned int c) {
|
|
||||||
const unsigned int mask = (CHAR_BIT * sizeof(n) - 1);
|
|
||||||
c &= mask;
|
|
||||||
return (n >> c) | (n << ((-c) & mask));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Quick calculation WebSocket. The process of calculating the mask is one of the performance bottlenecks
|
|
||||||
* of the entire websocket. The performance between the optimized version and the version without mask is not significant.
|
|
||||||
*
|
|
||||||
* We assume: the natural machine word length is 4byte (32bits) and the endianness is little-endian
|
|
||||||
* For xtensa: single fetch: 4 byte(32bit)
|
|
||||||
*
|
|
||||||
* @param data data buffer ptr
|
|
||||||
* @param mask websocket mask. Little-endian 32bis mask.
|
|
||||||
* @param len data length
|
|
||||||
*/
|
|
||||||
static void co_websocket_fast_mask(uint8_t *data, uint32_t mask, size_t len) {
|
|
||||||
uint32_t new_mask;
|
|
||||||
int align_len;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
const uint8_t *p_mask = (uint8_t *)&mask;
|
|
||||||
|
|
||||||
unsigned long int dst = (long int)data;
|
|
||||||
|
|
||||||
if (len >= 8) {
|
|
||||||
// copy just a few bytes to make dst aligned.
|
|
||||||
align_len = (-dst) % 4;
|
|
||||||
len -= align_len;
|
|
||||||
|
|
||||||
for (i = 0; i < align_len; i++) {
|
|
||||||
data[i] ^= p_mask[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the new mask on the aligned address
|
|
||||||
switch (align_len) {
|
|
||||||
case 1:
|
|
||||||
new_mask = co_rotr32(mask, 8U);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
new_mask = co_rotr32(mask, 16U);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
new_mask = co_rotr32(mask, 24U);
|
|
||||||
break;
|
|
||||||
default: // 0
|
|
||||||
new_mask = mask;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
p_mask = (uint8_t *)&new_mask;
|
|
||||||
|
|
||||||
dst += align_len;
|
|
||||||
|
|
||||||
for (i = 0; i < len / 4; i++) {
|
|
||||||
*((uint32_t *)dst) ^= new_mask;
|
|
||||||
dst += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
len %= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are just a few bytes to process
|
|
||||||
for (i = 0; i < len; i++) {
|
|
||||||
*((uint8_t *)dst) ^= p_mask[i % 4];
|
|
||||||
dst += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t co_websocket_get_new_mask(uint32_t mask, size_t len) {
|
|
||||||
switch (len & 0b11) {
|
|
||||||
case 1:
|
|
||||||
return co_rotr32(mask, 8U);
|
|
||||||
case 2:
|
|
||||||
return co_rotr32(mask, 16U);
|
|
||||||
case 3:
|
|
||||||
return co_rotr32(mask, 24U);
|
|
||||||
default:
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Process websocket payload
|
|
||||||
*
|
|
||||||
* @param cb corsacOTA control block
|
|
||||||
* @param scb corsacOTA socket control block
|
|
||||||
* @return co_err_t
|
|
||||||
* - CO_OK: Successful processing of all payloads
|
|
||||||
* - CO_ERROR_IO_PENDING: There are still new frames to be processed
|
|
||||||
*/
|
|
||||||
static co_err_t co_websocket_process_payload(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
int len, new_len;
|
|
||||||
uint8_t *data;
|
|
||||||
uint32_t mask;
|
|
||||||
|
|
||||||
data = (uint8_t *)scb->buf + scb->read_len;
|
|
||||||
// May be possible to read the complete frame and maybe a new frame rate afterwards
|
|
||||||
len = min(scb->remaining_len - scb->read_len, scb->wcb.payload_len);
|
|
||||||
|
|
||||||
// For ping frames, we will directly change their opcode and send.
|
|
||||||
if (scb->wcb.MASK == 1 && scb->wcb.OPCODE != WS_OPCODE_PING) {
|
|
||||||
mask = scb->wcb.mask.val;
|
|
||||||
co_websocket_fast_mask(data, mask, len);
|
|
||||||
|
|
||||||
scb->wcb.mask.val = co_websocket_get_new_mask(mask, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the previous processing, we can ensure that each new frame can begin in a place where the Buffer offset is 0.
|
|
||||||
switch (scb->wcb.OPCODE) {
|
|
||||||
case WS_OPCODE_TEXT:
|
|
||||||
#if (CO_TEST_MODE == 1)
|
|
||||||
co_websocket_send_echo(data, len, WS_OPCODE_TEXT);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
// case 0: This frame should be skip
|
|
||||||
if (scb->wcb.skip_frame) {
|
|
||||||
if (len == scb->wcb.payload_len) { // The last part of the data in this frame has been received
|
|
||||||
scb->wcb.skip_frame = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 1: Receive the entire payload
|
|
||||||
if (len == scb->wcb.payload_len && cb->recv_data_offset == 0) {
|
|
||||||
co_websocket_process_text(data, len);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: Part of the payload has been received before
|
|
||||||
if (len > CONFIG_CO_WS_TEXT_BUFFER_SIZE - cb->recv_data_offset) { // overflow
|
|
||||||
if (len < scb->wcb.payload_len) { // This frame has not yet been received
|
|
||||||
scb->wcb.skip_frame = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
co_websocket_send_msg_with_code(CO_RES_INVALID_SIZE, "request too long");
|
|
||||||
cb->recv_data_offset = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(cb->recv_data + cb->recv_data_offset, data, len);
|
|
||||||
cb->recv_data_offset += len;
|
|
||||||
|
|
||||||
if (len == scb->wcb.payload_len) {
|
|
||||||
co_websocket_process_text(cb->recv_data, len);
|
|
||||||
cb->recv_data_offset = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_BINARY:
|
|
||||||
#if (CO_TEST_MODE == 1)
|
|
||||||
co_websocket_send_echo(data, len, WS_OPCODE_BINARY);
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
//// TODO: check return val
|
|
||||||
co_websocket_process_binary(data, len);
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_PING:
|
|
||||||
co_websocket_process_ping(cb, scb);
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_PONG:
|
|
||||||
break;
|
|
||||||
case WS_OPCODE_CLOSE:
|
|
||||||
co_websocket_process_close(cb, scb);
|
|
||||||
return CO_FAIL; // close by server
|
|
||||||
default:
|
|
||||||
ESP_LOGE(CO_TAG, "unknow opcode: %d", scb->wcb.OPCODE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_len = scb->remaining_len - scb->read_len - len;
|
|
||||||
// case 0: New frames still exist
|
|
||||||
if (new_len > 0) {
|
|
||||||
// For simplicity, we make sure that the websocket header is always at the beginning of the buf.
|
|
||||||
memcpy(scb->buf, data + len, new_len);
|
|
||||||
|
|
||||||
scb->read_len = 0;
|
|
||||||
scb->remaining_len = new_len;
|
|
||||||
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
|
|
||||||
scb->wcb.payload_len = 0;
|
|
||||||
scb->wcb.payload_read_len = 0;
|
|
||||||
return CO_ERROR_IO_PENDING;
|
|
||||||
}
|
|
||||||
// case 1: The payload part is not yet fully read.
|
|
||||||
else if (scb->wcb.payload_len > len) {
|
|
||||||
scb->wcb.payload_len -= len;
|
|
||||||
|
|
||||||
scb->read_len = 0;
|
|
||||||
scb->remaining_len = 0;
|
|
||||||
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
// case 2: Exactly one complete frame is read and there is no remaining available data in buf.
|
|
||||||
else {
|
|
||||||
scb->read_len = 0;
|
|
||||||
scb->remaining_len = 0;
|
|
||||||
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
|
|
||||||
scb->wcb.payload_len = 0;
|
|
||||||
scb->wcb.payload_read_len = 0;
|
|
||||||
return CO_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t co_websocket_process(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
if (cb->websocket != scb) {
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fd, ret, offset;
|
|
||||||
|
|
||||||
fd = scb->fd;
|
|
||||||
offset = scb->remaining_len;
|
|
||||||
|
|
||||||
ret = recv(fd, scb->buf + offset, CONFIG_CO_SOCKET_BUFFER_SIZE - offset, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
scb->remaining_len += ret;
|
|
||||||
|
|
||||||
do {
|
|
||||||
// After we process a partial or complete payload,
|
|
||||||
// we always receive a new payload or header starting from the head of the buf.
|
|
||||||
if (scb->status != CO_SOCKET_WEBSOCKET_PAYLOAD) {
|
|
||||||
ret = co_websocket_process_header(cb, scb);
|
|
||||||
if (ret != CO_OK) {
|
|
||||||
return CO_FAIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perhaps we have already read the header section, and if there are extra bytes left over,
|
|
||||||
// we continue reading the payload section.
|
|
||||||
if (scb->status == CO_SOCKET_WEBSOCKET_PAYLOAD) {
|
|
||||||
ret = co_websocket_process_payload(cb, scb);
|
|
||||||
if (ret == CO_FAIL) {
|
|
||||||
return CO_FAIL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (ret == CO_ERROR_IO_PENDING);
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief parse HTTP header lines of format:
|
|
||||||
* \r\nfield_name: value1, value2, ... \r\n
|
|
||||||
*
|
|
||||||
* @param header_start
|
|
||||||
* @param header_end
|
|
||||||
* @param field_name
|
|
||||||
* @param value Optional value
|
|
||||||
* @return const char* The specific value starts, or the beginning of value in field.
|
|
||||||
*/
|
|
||||||
static const char *co_http_header_find_field_value(const char *header_start, const char *header_end, const char *field_name, const char *value) {
|
|
||||||
const char *field_start, *field_end, *next_crlf, *value_start;
|
|
||||||
int field_name_len;
|
|
||||||
|
|
||||||
field_name_len = (int)strlen(field_name);
|
|
||||||
|
|
||||||
field_start = header_start;
|
|
||||||
do {
|
|
||||||
field_start = strcasestr(field_start + 1, field_name);
|
|
||||||
field_end = field_start + field_name_len - 1;
|
|
||||||
|
|
||||||
if (field_start != NULL && field_start - header_start >= 2 && field_start[-2] == '\r' && field_start[-1] == '\n') { // is start with "\r\n" ?
|
|
||||||
if (header_end - field_end >= 1 && field_end[1] == ':') { // is end with ':' ?
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (field_start != NULL);
|
|
||||||
|
|
||||||
if (field_start == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the field terminator
|
|
||||||
next_crlf = strcasestr(field_start, "\r\n");
|
|
||||||
if (next_crlf == NULL) {
|
|
||||||
return NULL; // Malformed HTTP header!
|
|
||||||
}
|
|
||||||
|
|
||||||
// If not looking for a value, then return a pointer to the start of values string
|
|
||||||
if (value == NULL) {
|
|
||||||
return field_end + 2; // 2 for ':' ' '(blank)
|
|
||||||
}
|
|
||||||
|
|
||||||
value_start = strcasestr(field_start, value);
|
|
||||||
if (value_start == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value_start > next_crlf) {
|
|
||||||
return NULL; // encounter with unanticipated CRLF
|
|
||||||
}
|
|
||||||
|
|
||||||
// The value we found should be properly delineated from the other tokens
|
|
||||||
if (isalnum((unsigned char)value_start[-1]) || isalnum((unsigned char)value_start[strlen(value)])) {
|
|
||||||
// "field_name: value1, value2,"
|
|
||||||
// Consecutive tokens will be considered as errors.
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void co_http_error_400_response(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
const char *error = "HTTP/1.1 400 Bad Request\r\n\r\n";
|
|
||||||
send(scb->fd, error, strlen(error), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
||||||
|
|
||||||
static int co_sha1(const unsigned char *src, size_t src_len, unsigned char *dst) {
|
|
||||||
return mbedtls_sha1(src, src_len, dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int co_base64_encode(unsigned char *dst, size_t dst_len, size_t *written_len, unsigned char *src, size_t src_len) {
|
|
||||||
return mbedtls_base64_encode(dst, dst_len, written_len, src, src_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t co_websocket_create_accept_key(char *dst, size_t dst_len, const char *client_key) {
|
|
||||||
uint8_t sha1buf[20], key_src[60];
|
|
||||||
|
|
||||||
memcpy(key_src, client_key, 24);
|
|
||||||
memcpy(key_src + 24, WS_GUID, 36);
|
|
||||||
|
|
||||||
if (co_sha1(key_src, sizeof(key_src), sha1buf) != 0) {
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t base64_encode_len;
|
|
||||||
if (co_base64_encode((unsigned char *)dst, dst_len, &base64_encode_len, sha1buf, sizeof(sha1buf)) != 0) {
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add terminator
|
|
||||||
dst[base64_encode_len] = '\0';
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t co_websocket_handshake_send_key(int fd, const char *client_key) {
|
|
||||||
char res_header[256], accept_key[29];
|
|
||||||
int res_header_length;
|
|
||||||
|
|
||||||
if (co_websocket_create_accept_key(accept_key, sizeof(accept_key), client_key) != ESP_OK) {
|
|
||||||
ESP_LOGE(CO_TAG, LOG_FMT("fail to create accept key"));
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(res_header, sizeof(res_header),
|
|
||||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
|
||||||
"Server: corsacOTA server\r\n"
|
|
||||||
"Upgrade: websocket\r\n"
|
|
||||||
"Connection: Upgrade\r\n"
|
|
||||||
"Sec-WebSocket-Accept: %s\r\n"
|
|
||||||
"\r\n",
|
|
||||||
accept_key);
|
|
||||||
|
|
||||||
res_header_length = strlen(res_header);
|
|
||||||
send(fd, res_header, res_header_length, 0);
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static esp_err_t co_websocket_handshake_process(co_cb_t *cb, co_socket_cb_t *scb) {
|
|
||||||
if (scb->remaining_len == 0) {
|
|
||||||
memset(scb->buf, 0, CONFIG_CO_SOCKET_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int offset = scb->remaining_len;
|
|
||||||
int fd = scb->fd;
|
|
||||||
|
|
||||||
int ret = recv(fd, scb->buf + offset, CONFIG_CO_SOCKET_BUFFER_SIZE - offset, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
co_http_error_400_response(cb, scb);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
scb->remaining_len += ret;
|
|
||||||
|
|
||||||
// Already received the entire http header?
|
|
||||||
if (scb->remaining_len < 4 || memcmp(scb->buf + scb->remaining_len - 4, "\r\n\r\n", 4) != 0) {
|
|
||||||
return ESP_OK; // Not yet received
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *header_start = scb->buf, *header_end = scb->buf + scb->remaining_len - 1;
|
|
||||||
const char *ws_key_start, *ws_key_end;
|
|
||||||
|
|
||||||
if (co_http_header_find_field_value(header_start, header_end, "Upgrade", "websocket") == NULL ||
|
|
||||||
co_http_header_find_field_value(header_start, header_end, "Connection", "Upgrade") == NULL ||
|
|
||||||
(ws_key_start = co_http_header_find_field_value(header_start, header_end, "Sec-WebSocket-Key", NULL)) == NULL) {
|
|
||||||
co_http_error_400_response(cb, scb);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* example:
|
|
||||||
Sec-WebSocket-Key: c2REMVVpRXJRQWJ0Q1dKeQ==\r\n
|
|
||||||
|
|
|
||||||
ws_key_start
|
|
||||||
*/
|
|
||||||
|
|
||||||
// skip the extra blank
|
|
||||||
for (; *ws_key_start == ' '; ws_key_start++) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the end of ws key
|
|
||||||
for (ws_key_end = ws_key_start; *ws_key_end != '\r' && *ws_key_end != ' '; ws_key_end++) {
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* example:
|
|
||||||
Sec-WebSocket-Key: c2REMVVpRXJRQWJ0Q1dKeQ==\r\n
|
|
||||||
| ||
|
|
||||||
ws_key_start ws_key_end
|
|
||||||
*/
|
|
||||||
if (ws_key_end - ws_key_start != 24) {
|
|
||||||
co_http_error_400_response(cb, scb);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (co_websocket_handshake_send_key(scb->fd, ws_key_start) != ESP_OK) {
|
|
||||||
co_http_error_400_response(cb, scb);
|
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(CO_TAG, "websocket handshake success");
|
|
||||||
|
|
||||||
cb->websocket = scb;
|
|
||||||
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
|
|
||||||
scb->remaining_len = 0;
|
|
||||||
|
|
||||||
memset(scb->buf, 0, CONFIG_CO_SOCKET_BUFFER_SIZE);
|
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void websocket_buffer_malloc() {
|
|
||||||
if (ws_process_buffer != NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
free_dap_ringbuf();
|
|
||||||
ws_process_buffer = malloc(1200);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void websocket_buffer_free() {
|
|
||||||
if (ws_process_buffer != NULL) {
|
|
||||||
free(ws_process_buffer);
|
|
||||||
ws_process_buffer = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void co_websocket_process_dap(uint8_t *data, size_t len) {
|
|
||||||
uint8_t *buf;
|
|
||||||
int max_offset, res, offset;
|
|
||||||
|
|
||||||
max_offset = co_websocket_get_res_payload_offset(1500);
|
|
||||||
buf = ws_process_buffer + max_offset;
|
|
||||||
|
|
||||||
res = DAP_ExecuteCommand(data, buf);
|
|
||||||
res &= 0xFFFF;
|
|
||||||
|
|
||||||
offset = co_websocket_get_res_payload_offset(res);
|
|
||||||
buf -= offset;
|
|
||||||
|
|
||||||
co_websocket_send_frame(buf, res, WS_OPCODE_BINARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
int websocket_worker(int fd, uint8_t *base, uint32_t length) {
|
|
||||||
co_cb_t cb;
|
|
||||||
co_socket_cb_t scb;
|
|
||||||
esp_err_t ret;
|
|
||||||
|
|
||||||
memset(&cb, 0, sizeof(co_cb_t));
|
|
||||||
memset(&scb, 0, sizeof(co_socket_cb_t));
|
|
||||||
|
|
||||||
cb.recv_data = NULL; // used in websocket text mode
|
|
||||||
cb.websocket = &scb;
|
|
||||||
|
|
||||||
scb.fd = fd;
|
|
||||||
scb.status = CO_SOCKET_HANDSHAKE;
|
|
||||||
scb.buf = (char *)base;
|
|
||||||
scb.remaining_len = 4; // already read 4 byte
|
|
||||||
|
|
||||||
global_cb = &cb;
|
|
||||||
|
|
||||||
// handshake
|
|
||||||
do {
|
|
||||||
ret = co_websocket_handshake_process(&cb, &scb);
|
|
||||||
if (ret != ESP_OK)
|
|
||||||
return ret;
|
|
||||||
} while (scb.status == CO_SOCKET_HANDSHAKE);
|
|
||||||
|
|
||||||
websocket_buffer_malloc();
|
|
||||||
|
|
||||||
// websocket data process
|
|
||||||
do {
|
|
||||||
ret = co_websocket_process(&cb, &scb);
|
|
||||||
if (ret != ESP_OK)
|
|
||||||
goto out;
|
|
||||||
} while (1);
|
|
||||||
|
|
||||||
|
|
||||||
out:
|
|
||||||
websocket_buffer_free();
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
#ifndef __WEBSOCKET_SERVER_H__
|
|
||||||
#define __WEBSOCKET_SERVER_H__
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
int websocket_worker(int fd, uint8_t *base, uint32_t length);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -12,16 +12,6 @@
|
||||||
#define WIFI_DEFAULT_STA_SSID "example_ssid"
|
#define WIFI_DEFAULT_STA_SSID "example_ssid"
|
||||||
#define WIFI_DEFAULT_STA_PASS "12345678"
|
#define WIFI_DEFAULT_STA_PASS "12345678"
|
||||||
|
|
||||||
#if defined CONFIG_IDF_TARGET_ESP32
|
|
||||||
#define WIFI_LED_ENABLE 0
|
|
||||||
#elif defined CONFIG_IDF_TARGET_ESP32C3
|
|
||||||
#define WIFI_LED_ENABLE 0
|
|
||||||
#elif defined CONFIG_IDF_TARGET_ESP32S3
|
|
||||||
#define WIFI_LED_ENABLE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define WIFI_LED_PIN 9
|
|
||||||
|
|
||||||
#define IP4_ADDR_EXPAND(...) IP4_ADDR(__VA_ARGS__)
|
#define IP4_ADDR_EXPAND(...) IP4_ADDR(__VA_ARGS__)
|
||||||
|
|
||||||
#endif //WIFI_CONFIGURATION_H_GUARD
|
#endif //WIFI_CONFIGURATION_H_GUARD
|
|
@ -14,10 +14,6 @@
|
||||||
#include <lwip/ip4_addr.h>
|
#include <lwip/ip4_addr.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <hal/gpio_types.h>
|
|
||||||
#include <driver/ledc.h>
|
|
||||||
#include <hal/gpio_hal.h>
|
|
||||||
#include <soc/ledc_periph.h>
|
|
||||||
|
|
||||||
#define TAG __FILENAME__
|
#define TAG __FILENAME__
|
||||||
|
|
||||||
|
@ -53,16 +49,10 @@ static void set_sta_cred(const char *ssid, const char *password);
|
||||||
static void disconn_handler(void);
|
static void disconn_handler(void);
|
||||||
static int set_default_sta_cred(void);
|
static int set_default_sta_cred(void);
|
||||||
|
|
||||||
static void wifi_led_init();
|
|
||||||
static void wifi_led_set_blink();
|
|
||||||
static void wifi_led_set_on();
|
|
||||||
|
|
||||||
void wifi_manager_init(void)
|
void wifi_manager_init(void)
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
uint8_t do_connect = 0;
|
uint8_t do_connect = 0;
|
||||||
wifi_led_init();
|
|
||||||
wifi_led_set_blink();
|
|
||||||
|
|
||||||
ap_netif = esp_netif_create_default_wifi_ap();
|
ap_netif = esp_netif_create_default_wifi_ap();
|
||||||
assert(ap_netif);
|
assert(ap_netif);
|
||||||
|
@ -133,55 +123,6 @@ void wifi_manager_init(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void wifi_led_init()
|
|
||||||
{
|
|
||||||
#if WIFI_LED_ENABLE
|
|
||||||
ledc_timer_config_t ledc_timer = {
|
|
||||||
.duty_resolution = LEDC_TIMER_14_BIT, // resolution of PWM duty
|
|
||||||
.freq_hz = 5, // frequency of PWM signal
|
|
||||||
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
|
|
||||||
.timer_num = LEDC_TIMER_0, // timer index
|
|
||||||
.clk_cfg = LEDC_USE_APB_CLK, // Auto select the source clock
|
|
||||||
};
|
|
||||||
ledc_timer_config(&ledc_timer);
|
|
||||||
|
|
||||||
ledc_channel_config_t ledc_channel = {
|
|
||||||
.channel = LEDC_CHANNEL_0,
|
|
||||||
.duty = 0,
|
|
||||||
.gpio_num = WIFI_LED_PIN,
|
|
||||||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
|
||||||
.hpoint = 1,
|
|
||||||
.timer_sel = LEDC_TIMER_0,
|
|
||||||
.flags.output_invert = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
ledc_channel_config(&ledc_channel);
|
|
||||||
ledc_fade_func_install(0);
|
|
||||||
|
|
||||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[WIFI_LED_PIN], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_direction(WIFI_LED_PIN, GPIO_MODE_OUTPUT_OD);
|
|
||||||
gpio_set_direction(WIFI_LED_PIN, GPIO_FLOATING);
|
|
||||||
esp_rom_gpio_connect_out_signal(WIFI_LED_PIN, ledc_periph_signal[LEDC_LOW_SPEED_MODE].sig_out0_idx + LEDC_CHANNEL_0,
|
|
||||||
1, 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void wifi_led_set_blink()
|
|
||||||
{
|
|
||||||
#if WIFI_LED_ENABLE
|
|
||||||
ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 12), 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void wifi_led_set_on()
|
|
||||||
{
|
|
||||||
#if WIFI_LED_ENABLE
|
|
||||||
ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 14)-1, 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief called by wifi_event_handler on scan done
|
* @brief called by wifi_event_handler on scan done
|
||||||
* */
|
* */
|
||||||
|
@ -329,7 +270,6 @@ int wifi_manager_connect(const char *ssid, const char *password)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* connection success: overwrite last connected credential */
|
/* connection success: overwrite last connected credential */
|
||||||
wifi_led_set_on();
|
|
||||||
wifi_credential_t credential;
|
wifi_credential_t credential;
|
||||||
memcpy(credential.ssid, ssid, 32);
|
memcpy(credential.ssid, ssid, 32);
|
||||||
memcpy(credential.password, password, 64);
|
memcpy(credential.password, password, 64);
|
||||||
|
@ -377,9 +317,6 @@ static void reconnection_task(void *arg)
|
||||||
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20000));
|
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20000));
|
||||||
if (ctx.conn.event || ctx.auto_reconnect == 0) {
|
if (ctx.conn.event || ctx.auto_reconnect == 0) {
|
||||||
/* reconnection successful or stop reconnect */
|
/* reconnection successful or stop reconnect */
|
||||||
if (ctx.conn.event) {
|
|
||||||
wifi_led_set_on();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,7 +342,7 @@ static void disconn_handler(void)
|
||||||
* 2. WI-FI AP or AP device is closed
|
* 2. WI-FI AP or AP device is closed
|
||||||
* 3. this device is ejected by AP
|
* 3. this device is ejected by AP
|
||||||
* */
|
* */
|
||||||
wifi_led_set_blink();
|
|
||||||
if (ctx.auto_reconnect == 0) {
|
if (ctx.auto_reconnect == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue