|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Unlicense OR CC0-1.0 |
| 5 | + */ |
| 6 | +/* ESP libwebsockets server example |
| 7 | +
|
| 8 | + This example code is in the Public Domain (or CC0 licensed, at your option.) |
| 9 | +
|
| 10 | + Unless required by applicable law or agreed to in writing, this |
| 11 | + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR |
| 12 | + CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +*/ |
| 14 | + |
| 15 | +#include <libwebsockets.h> |
| 16 | +#include <stdio.h> |
| 17 | + |
| 18 | +#include "esp_wifi.h" |
| 19 | +#include "esp_system.h" |
| 20 | +#include "nvs_flash.h" |
| 21 | +#include "protocol_examples_common.h" |
| 22 | + |
| 23 | +#include "freertos/FreeRTOS.h" |
| 24 | +#include "freertos/task.h" |
| 25 | + |
| 26 | +#include "esp_log.h" |
| 27 | +#include "esp_netif.h" |
| 28 | +#include "esp_netif_ip_addr.h" |
| 29 | + |
| 30 | +#define RING_DEPTH 4096 |
| 31 | +#define LWS_MAX_PAYLOAD 1024 |
| 32 | + |
| 33 | +static int callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason, |
| 34 | + void *user, void *in, size_t len); |
| 35 | +/* one of these created for each message */ |
| 36 | +struct msg { |
| 37 | + void *payload; /* is malloc'd */ |
| 38 | + size_t len; |
| 39 | +}; |
| 40 | + |
| 41 | +/* one of these is created for each client connecting to us */ |
| 42 | + |
| 43 | +struct per_session_data__minimal { |
| 44 | + struct per_session_data__minimal *pss_list; |
| 45 | + struct lws *wsi; |
| 46 | + int last; /* the last message number we sent */ |
| 47 | + unsigned char buffer[RING_DEPTH]; |
| 48 | + size_t buffer_len; |
| 49 | + int is_receiving_fragments; |
| 50 | + int is_ready_to_send; |
| 51 | +}; |
| 52 | + |
| 53 | +/* one of these is created for each vhost our protocol is used with */ |
| 54 | + |
| 55 | +struct per_vhost_data__minimal { |
| 56 | + struct lws_context *context; |
| 57 | + struct lws_vhost *vhost; |
| 58 | + const struct lws_protocols *protocol; |
| 59 | + |
| 60 | + struct per_session_data__minimal *pss_list; /* linked-list of live pss*/ |
| 61 | + |
| 62 | + struct msg amsg; /* the one pending message... */ |
| 63 | + int current; /* the current message number we are caching */ |
| 64 | +}; |
| 65 | + |
| 66 | +static struct lws_protocols protocols[] = { |
| 67 | + { |
| 68 | + .name = "lws-minimal-server-echo", |
| 69 | + .callback = callback_minimal_server_echo, |
| 70 | + .per_session_data_size = sizeof(struct per_session_data__minimal), |
| 71 | + .rx_buffer_size = RING_DEPTH, |
| 72 | + .id = 0, |
| 73 | + .user = NULL, |
| 74 | + .tx_packet_size = RING_DEPTH |
| 75 | + }, |
| 76 | + LWS_PROTOCOL_LIST_TERM |
| 77 | +}; |
| 78 | + |
| 79 | +static int options; |
| 80 | +static const char *TAG = "lws-server-echo", *iface = ""; |
| 81 | + |
| 82 | +/* pass pointers to shared vars to the protocol */ |
| 83 | +static const struct lws_protocol_vhost_options pvo_options = { |
| 84 | + NULL, |
| 85 | + NULL, |
| 86 | + "options", /* pvo name */ |
| 87 | + (void *) &options /* pvo value */ |
| 88 | +}; |
| 89 | + |
| 90 | +static const struct lws_protocol_vhost_options pvo_interrupted = { |
| 91 | + &pvo_options, |
| 92 | + NULL, |
| 93 | + "interrupted", /* pvo name */ |
| 94 | + NULL /* pvo value */ |
| 95 | +}; |
| 96 | + |
| 97 | +static const struct lws_protocol_vhost_options pvo = { |
| 98 | + NULL, /* "next" pvo linked-list */ |
| 99 | + &pvo_interrupted, /* "child" pvo linked-list */ |
| 100 | + "lws-minimal-server-echo", /* protocol name we belong to on this vhost */ |
| 101 | + "" /* ignored */ |
| 102 | +}; |
| 103 | + |
| 104 | +int app_main(int argc, const char **argv) |
| 105 | +{ |
| 106 | + ESP_LOGI(TAG, "[APP] Startup.."); |
| 107 | + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); |
| 108 | + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); |
| 109 | + esp_log_level_set("*", ESP_LOG_INFO); |
| 110 | + |
| 111 | + ESP_ERROR_CHECK(nvs_flash_init()); |
| 112 | + ESP_ERROR_CHECK(esp_netif_init()); |
| 113 | + ESP_ERROR_CHECK(esp_event_loop_create_default()); |
| 114 | + |
| 115 | + /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. |
| 116 | + * Read "Establishing Wi-Fi or Ethernet Connection" section in |
| 117 | + * examples/protocols/README.md for more information about this function. |
| 118 | + */ |
| 119 | + ESP_ERROR_CHECK(example_connect()); |
| 120 | + |
| 121 | + /* Create LWS Context - Server. */ |
| 122 | + struct lws_context_creation_info info; |
| 123 | + struct lws_context *context; |
| 124 | + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; |
| 125 | + |
| 126 | + lws_set_log_level(logs, NULL); |
| 127 | + ESP_LOGI(TAG, "LWS minimal ws server echo\n"); |
| 128 | + |
| 129 | + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ |
| 130 | + info.port = CONFIG_WEBSOCKET_PORT; |
| 131 | + info.iface = iface; |
| 132 | + info.protocols = protocols; |
| 133 | + info.pvo = &pvo; |
| 134 | + info.pt_serv_buf_size = 64 * 1024; |
| 135 | + |
| 136 | +#ifdef CONFIG_WS_OVER_TLS |
| 137 | + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE; |
| 138 | + |
| 139 | + /* Configuring server certificates for mutual authentification */ |
| 140 | + extern const char cert_start[] asm("_binary_server_cert_pem_start"); // Server certificate |
| 141 | + extern const char cert_end[] asm("_binary_server_cert_pem_end"); |
| 142 | + extern const char key_start[] asm("_binary_server_key_pem_start"); // Server private key |
| 143 | + extern const char key_end[] asm("_binary_server_key_pem_end"); |
| 144 | + extern const char cacert_start[] asm("_binary_ca_cert_pem_start"); // CA certificate |
| 145 | + extern const char cacert_end[] asm("_binary_ca_cert_pem_end"); |
| 146 | + |
| 147 | + info.server_ssl_cert_mem = cert_start; |
| 148 | + info.server_ssl_cert_mem_len = cert_end - cert_start - 1; |
| 149 | + info.server_ssl_private_key_mem = key_start; |
| 150 | + info.server_ssl_private_key_mem_len = key_end - key_start - 1; |
| 151 | + info.server_ssl_ca_mem = cacert_start; |
| 152 | + info.server_ssl_ca_mem_len = cacert_end - cacert_start; |
| 153 | +#endif |
| 154 | + |
| 155 | + context = lws_create_context(&info); |
| 156 | + if (!context) { |
| 157 | + ESP_LOGE(TAG, "lws init failed\n"); |
| 158 | + return 1; |
| 159 | + } |
| 160 | + |
| 161 | + while (n >= 0) { |
| 162 | + n = lws_service(context, 100); |
| 163 | + } |
| 164 | + |
| 165 | + lws_context_destroy(context); |
| 166 | + |
| 167 | + return 0; |
| 168 | +} |
| 169 | + |
| 170 | +static int callback_minimal_server_echo(struct lws *wsi, enum lws_callback_reasons reason, |
| 171 | + void *user, void *in, size_t len) |
| 172 | +{ |
| 173 | + struct per_session_data__minimal *pss = (struct per_session_data__minimal *)user; |
| 174 | + struct per_vhost_data__minimal *vhd = (struct per_vhost_data__minimal *) |
| 175 | + lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); |
| 176 | + char client_address[128]; |
| 177 | + |
| 178 | + switch (reason) { |
| 179 | + case LWS_CALLBACK_PROTOCOL_INIT: |
| 180 | + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), |
| 181 | + lws_get_protocol(wsi), |
| 182 | + sizeof(struct per_vhost_data__minimal)); |
| 183 | + if (!vhd) { |
| 184 | + ESP_LOGE("LWS_SERVER", "Failed to allocate vhost data."); |
| 185 | + return -1; |
| 186 | + } |
| 187 | + vhd->context = lws_get_context(wsi); |
| 188 | + vhd->protocol = lws_get_protocol(wsi); |
| 189 | + vhd->vhost = lws_get_vhost(wsi); |
| 190 | + vhd->current = 0; |
| 191 | + vhd->amsg.payload = NULL; |
| 192 | + vhd->amsg.len = 0; |
| 193 | + break; |
| 194 | + |
| 195 | + case LWS_CALLBACK_ESTABLISHED: |
| 196 | + lws_get_peer_simple(wsi, client_address, sizeof(client_address)); |
| 197 | + ESP_LOGI("LWS_SERVER", "New client connected: %s", client_address); |
| 198 | + lws_ll_fwd_insert(pss, pss_list, vhd->pss_list); |
| 199 | + pss->wsi = wsi; |
| 200 | + pss->last = vhd->current; |
| 201 | + pss->buffer_len = 0; |
| 202 | + pss->is_receiving_fragments = 0; |
| 203 | + pss->is_ready_to_send = 0; |
| 204 | + memset(pss->buffer, 0, RING_DEPTH); |
| 205 | + break; |
| 206 | + |
| 207 | + case LWS_CALLBACK_CLOSED: |
| 208 | + lws_get_peer_simple(wsi, client_address, sizeof(client_address)); |
| 209 | + ESP_LOGI("LWS_SERVER", "Client disconnected: %s", client_address); |
| 210 | + lws_ll_fwd_remove(struct per_session_data__minimal, pss_list, pss, vhd->pss_list); |
| 211 | + break; |
| 212 | + |
| 213 | + case LWS_CALLBACK_RECEIVE: |
| 214 | + lws_get_peer_simple(wsi, client_address, sizeof(client_address)); |
| 215 | + |
| 216 | + bool is_binary = lws_frame_is_binary(wsi); /* Identify if it is binary or text */ |
| 217 | + |
| 218 | + ESP_LOGI("LWS_SERVER", "%s fragment received from %s (%d bytes)", |
| 219 | + is_binary ? "Binary" : "Text", client_address, (int)len); |
| 220 | + |
| 221 | + if (lws_is_first_fragment(wsi)) { /* First fragment: reset the buffer */ |
| 222 | + pss->buffer_len = 0; |
| 223 | + } |
| 224 | + |
| 225 | + if (pss->buffer_len + len < RING_DEPTH) { |
| 226 | + memcpy(pss->buffer + pss->buffer_len, in, len); |
| 227 | + pss->buffer_len += len; |
| 228 | + } else { |
| 229 | + ESP_LOGE("LWS_SERVER", "Fragmented message exceeded buffer limit."); |
| 230 | + return -1; |
| 231 | + } |
| 232 | + |
| 233 | + /* If it is the last part of the fragment, process the complete message */ |
| 234 | + if (lws_is_final_fragment(wsi)) { |
| 235 | + ESP_LOGI("LWS_SERVER", "Complete %s message received from %s (%d bytes)", |
| 236 | + is_binary ? "binary" : "text", client_address, (int)pss->buffer_len); |
| 237 | + |
| 238 | + if (!is_binary) { |
| 239 | + ESP_LOGI("LWS_SERVER", "Complete text message: %.*s", (int)pss->buffer_len, (char *)pss->buffer); |
| 240 | + } else { |
| 241 | + char hex_output[pss->buffer_len * 2 + 1]; /* Display the binary message as hexadecimal */ |
| 242 | + for (int i = 0; i < pss->buffer_len; i++) { |
| 243 | + snprintf(&hex_output[i * 2], 3, "%02X", pss->buffer[i]); |
| 244 | + } |
| 245 | + ESP_LOGI("LWS_SERVER", "Complete binary message (hex): %s", hex_output); |
| 246 | + } |
| 247 | + |
| 248 | + /* Respond to the client */ |
| 249 | + int write_type = is_binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT; |
| 250 | + int m = lws_write(wsi, (unsigned char *)pss->buffer, pss->buffer_len, write_type); |
| 251 | + pss->buffer_len = 0; |
| 252 | + |
| 253 | + if (m < (int)pss->buffer_len) { |
| 254 | + ESP_LOGE("LWS_SERVER", "Failed to send %s message.", is_binary ? "binary" : "text"); |
| 255 | + return -1; |
| 256 | + } |
| 257 | + break; |
| 258 | + } |
| 259 | + |
| 260 | + /* If the message is not fragmented, process JSON, echo, and other messages */ |
| 261 | + |
| 262 | + /* JSON */ |
| 263 | + if (strstr((char *)in, "{") && strstr((char *)in, "}")) { |
| 264 | + ESP_LOGI("LWS_SERVER", "JSON message received from %s: %.*s", client_address, (int)len, (char *)in); |
| 265 | + int m = lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_TEXT); |
| 266 | + if (m < (int)len) { |
| 267 | + ESP_LOGE("LWS_SERVER", "Failed to send JSON message."); |
| 268 | + return -1; |
| 269 | + } |
| 270 | + break; |
| 271 | + } |
| 272 | + |
| 273 | + /* Echo */ |
| 274 | + if (!is_binary) { |
| 275 | + ESP_LOGI("LWS_SERVER", "Text message received from %s (%d bytes): %.*s", |
| 276 | + client_address, (int)len, (int)len, (char *)in); |
| 277 | + int m = lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_TEXT); |
| 278 | + if (m < (int)len) { |
| 279 | + ESP_LOGE("LWS_SERVER", "Failed to send text message."); |
| 280 | + return -1; |
| 281 | + } |
| 282 | + break; |
| 283 | + } |
| 284 | + |
| 285 | + /* Bin */ |
| 286 | + ESP_LOGI("LWS_SERVER", "Binary message received from %s (%d bytes)", client_address, (int)len); |
| 287 | + int m = lws_write(wsi, (unsigned char *)in, len, LWS_WRITE_BINARY); |
| 288 | + if (m < (int)len) { |
| 289 | + ESP_LOGE("LWS_SERVER", "Failed to send binary message."); |
| 290 | + return -1; |
| 291 | + } |
| 292 | + |
| 293 | + ESP_LOGI("LWS_SERVER", "Message sent back to client."); |
| 294 | + break; |
| 295 | + |
| 296 | + default: |
| 297 | + break; |
| 298 | + } |
| 299 | + |
| 300 | + return 0; |
| 301 | +} |
0 commit comments