/ components / esp_websocket_client / esp_websocket_client.c
esp_websocket_client.c
  1  // Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  
 15  #include <stdio.h>
 16  
 17  #include "esp_websocket_client.h"
 18  #include "esp_transport.h"
 19  #include "esp_transport_tcp.h"
 20  #include "esp_transport_ssl.h"
 21  #include "esp_transport_ws.h"
 22  /* using uri parser */
 23  #include "http_parser.h"
 24  #include "freertos/task.h"
 25  #include "freertos/semphr.h"
 26  #include "freertos/queue.h"
 27  #include "freertos/event_groups.h"
 28  #include "esp_log.h"
 29  #include "esp_timer.h"
 30  
 31  static const char *TAG = "WEBSOCKET_CLIENT";
 32  
 33  #define WEBSOCKET_TCP_DEFAULT_PORT      (80)
 34  #define WEBSOCKET_SSL_DEFAULT_PORT      (443)
 35  #define WEBSOCKET_BUFFER_SIZE_BYTE      (1024)
 36  #define WEBSOCKET_RECONNECT_TIMEOUT_MS  (10*1000)
 37  #define WEBSOCKET_TASK_PRIORITY         (5)
 38  #define WEBSOCKET_TASK_STACK            (4*1024)
 39  #define WEBSOCKET_NETWORK_TIMEOUT_MS    (10*1000)
 40  #define WEBSOCKET_PING_TIMEOUT_MS       (10*1000)
 41  #define WEBSOCKET_EVENT_QUEUE_SIZE      (1)
 42  #define WEBSOCKET_PINGPONG_TIMEOUT_SEC  (120)
 43  
 44  #define ESP_WS_CLIENT_MEM_CHECK(TAG, a, action) if (!(a)) {                                         \
 45          ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted");                     \
 46          action;                                                                                     \
 47          }
 48  
 49  #define ESP_WS_CLIENT_STATE_CHECK(TAG, a, action) if ((a->state) < WEBSOCKET_STATE_INIT) {                                         \
 50          ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Websocket already stop");       \
 51          action;                                                                                     \
 52          }
 53  
 54  const static int STOPPED_BIT = BIT0;
 55  const static int CLOSE_FRAME_SENT_BIT = BIT1;   // Indicates that a close frame was sent by the client
 56                                          // and we are waiting for the server to continue with clean close
 57  
 58  ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
 59  
 60  typedef struct {
 61      int                         task_stack;
 62      int                         task_prio;
 63      char                        *uri;
 64      char                        *host;
 65      char                        *path;
 66      char                        *scheme;
 67      char                        *username;
 68      char                        *password;
 69      int                         port;
 70      bool                        auto_reconnect;
 71      void                        *user_context;
 72      int                         network_timeout_ms;
 73      char                        *subprotocol;
 74      char                        *user_agent;
 75      char                        *headers;
 76      int                         pingpong_timeout_sec;
 77  } websocket_config_storage_t;
 78  
 79  typedef enum {
 80      WEBSOCKET_STATE_ERROR = -1,
 81      WEBSOCKET_STATE_UNKNOW = 0,
 82      WEBSOCKET_STATE_INIT,
 83      WEBSOCKET_STATE_CONNECTED,
 84      WEBSOCKET_STATE_WAIT_TIMEOUT,
 85      WEBSOCKET_STATE_CLOSING,
 86  } websocket_client_state_t;
 87  
 88  struct esp_websocket_client {
 89      esp_event_loop_handle_t     event_handle;
 90      TaskHandle_t                task_handle;
 91      esp_transport_list_handle_t transport_list;
 92      esp_transport_handle_t      transport;
 93      websocket_config_storage_t *config;
 94      websocket_client_state_t    state;
 95      uint64_t                    keepalive_tick_ms;
 96      uint64_t                    reconnect_tick_ms;
 97      uint64_t                    ping_tick_ms;
 98      uint64_t                    pingpong_tick_ms;
 99      int                         wait_timeout_ms;
100      int                         auto_reconnect;
101      bool                        run;
102      bool                        wait_for_pong_resp;
103      EventGroupHandle_t          status_bits;
104      xSemaphoreHandle            lock;
105      char                        *rx_buffer;
106      char                        *tx_buffer;
107      int                         buffer_size;
108      ws_transport_opcodes_t      last_opcode;
109      int                         payload_len;
110      int                         payload_offset;
111  };
112  
113  static uint64_t _tick_get_ms(void)
114  {
115      return esp_timer_get_time()/1000;
116  }
117  
118  static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle_t client,
119          esp_websocket_event_id_t event,
120          const char *data,
121          int data_len)
122  {
123      esp_err_t err;
124      esp_websocket_event_data_t event_data;
125  
126      event_data.client = client;
127      event_data.user_context = client->config->user_context;
128      event_data.data_ptr = data;
129      event_data.data_len = data_len;
130      event_data.op_code = client->last_opcode;
131      event_data.payload_len = client->payload_len;
132      event_data.payload_offset = client->payload_offset;
133  
134      if ((err = esp_event_post_to(client->event_handle,
135                                   WEBSOCKET_EVENTS, event,
136                                   &event_data,
137                                   sizeof(esp_websocket_event_data_t),
138                                   portMAX_DELAY)) != ESP_OK) {
139          return err;
140      }
141      return esp_event_loop_run(client->event_handle, 0);
142  }
143  
144  static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_handle_t client)
145  {
146      ESP_WS_CLIENT_STATE_CHECK(TAG, client, return ESP_FAIL);
147      esp_transport_close(client->transport);
148  
149      if (client->config->auto_reconnect) {
150          client->wait_timeout_ms = WEBSOCKET_RECONNECT_TIMEOUT_MS;
151          client->reconnect_tick_ms = _tick_get_ms();
152          ESP_LOGI(TAG, "Reconnect after %d ms", client->wait_timeout_ms);
153      }
154      client->state = WEBSOCKET_STATE_WAIT_TIMEOUT;
155      esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DISCONNECTED, NULL, 0);
156      return ESP_OK;
157  }
158  
159  static esp_err_t esp_websocket_client_set_config(esp_websocket_client_handle_t client, const esp_websocket_client_config_t *config)
160  {
161      websocket_config_storage_t *cfg = client->config;
162      cfg->task_prio = config->task_prio;
163      if (cfg->task_prio <= 0) {
164          cfg->task_prio = WEBSOCKET_TASK_PRIORITY;
165      }
166  
167      cfg->task_stack = config->task_stack;
168      if (cfg->task_stack == 0) {
169          cfg->task_stack = WEBSOCKET_TASK_STACK;
170      }
171  
172      if (config->host) {
173          cfg->host = strdup(config->host);
174          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->host, return ESP_ERR_NO_MEM);
175      }
176  
177      if (config->port) {
178          cfg->port = config->port;
179      }
180  
181      if (config->username) {
182          free(cfg->username);
183          cfg->username = strdup(config->username);
184          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->username, return ESP_ERR_NO_MEM);
185      }
186  
187      if (config->password) {
188          free(cfg->password);
189          cfg->password = strdup(config->password);
190          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->password, return ESP_ERR_NO_MEM);
191      }
192  
193      if (config->uri) {
194          free(cfg->uri);
195          cfg->uri = strdup(config->uri);
196          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->uri, return ESP_ERR_NO_MEM);
197      }
198      if (config->path) {
199          free(cfg->path);
200          cfg->path = strdup(config->path);
201          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->path, return ESP_ERR_NO_MEM);
202      }
203      if (config->subprotocol) {
204          free(cfg->subprotocol);
205          cfg->subprotocol = strdup(config->subprotocol);
206          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->subprotocol, return ESP_ERR_NO_MEM);
207      }
208      if (config->user_agent) {
209          free(cfg->user_agent);
210          cfg->user_agent = strdup(config->user_agent);
211          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->user_agent, return ESP_ERR_NO_MEM);
212      }
213      if (config->headers) {
214          free(cfg->headers);
215          cfg->headers = strdup(config->headers);
216          ESP_WS_CLIENT_MEM_CHECK(TAG, cfg->headers, return ESP_ERR_NO_MEM);
217      }
218  
219      cfg->network_timeout_ms = WEBSOCKET_NETWORK_TIMEOUT_MS;
220      cfg->user_context = config->user_context;
221      cfg->auto_reconnect = true;
222      if (config->disable_auto_reconnect) {
223          cfg->auto_reconnect = false;
224      }
225  
226      if (config->disable_pingpong_discon){
227          cfg->pingpong_timeout_sec = 0;
228      } else if (config->pingpong_timeout_sec) {
229          cfg->pingpong_timeout_sec = config->pingpong_timeout_sec;
230      } else {
231          cfg->pingpong_timeout_sec = WEBSOCKET_PINGPONG_TIMEOUT_SEC;
232      }
233  
234      return ESP_OK;
235  }
236  
237  static esp_err_t esp_websocket_client_destroy_config(esp_websocket_client_handle_t client)
238  {
239      if (client == NULL) {
240          return ESP_ERR_INVALID_ARG;
241      }
242      websocket_config_storage_t *cfg = client->config;
243      if (client->config == NULL) {
244          return ESP_ERR_INVALID_ARG;
245      }
246      free(cfg->host);
247      free(cfg->uri);
248      free(cfg->path);
249      free(cfg->scheme);
250      free(cfg->username);
251      free(cfg->password);
252      free(cfg->subprotocol);
253      free(cfg->user_agent);
254      free(cfg->headers);
255      memset(cfg, 0, sizeof(websocket_config_storage_t));
256      free(client->config);
257      client->config = NULL;
258      return ESP_OK;
259  }
260  
261  static void set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, esp_transport_handle_t trans)
262  {
263      if (trans && client->config->path) {
264          esp_transport_ws_set_path(trans, client->config->path);
265      }
266      if (trans && client->config->subprotocol) {
267          esp_transport_ws_set_subprotocol(trans, client->config->subprotocol);
268      }
269      if (trans && client->config->user_agent) {
270          esp_transport_ws_set_user_agent(trans, client->config->user_agent);
271      }
272      if (trans && client->config->headers) {
273          esp_transport_ws_set_headers(trans, client->config->headers);
274      }
275  }
276  
277  esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
278  {
279      esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
280      ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);
281  
282      esp_event_loop_args_t event_args = {
283          .queue_size = WEBSOCKET_EVENT_QUEUE_SIZE,
284          .task_name = NULL // no task will be created
285      };
286  
287      if (esp_event_loop_create(&event_args, &client->event_handle) != ESP_OK) {
288          ESP_LOGE(TAG, "Error create event handler for websocket client");
289          free(client);
290          return NULL;
291      }
292  
293      client->lock = xSemaphoreCreateRecursiveMutex();
294      ESP_WS_CLIENT_MEM_CHECK(TAG, client->lock, goto _websocket_init_fail);
295  
296      client->config = calloc(1, sizeof(websocket_config_storage_t));
297      ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
298  
299      client->transport_list = esp_transport_list_init();
300      ESP_WS_CLIENT_MEM_CHECK(TAG, client->transport_list, goto _websocket_init_fail);
301  
302      esp_transport_handle_t tcp = esp_transport_tcp_init();
303      ESP_WS_CLIENT_MEM_CHECK(TAG, tcp, goto _websocket_init_fail);
304  
305      esp_transport_set_default_port(tcp, WEBSOCKET_TCP_DEFAULT_PORT);
306      esp_transport_list_add(client->transport_list, tcp, "_tcp"); // need to save to transport list, for cleanup
307  
308  
309      esp_transport_handle_t ws = esp_transport_ws_init(tcp);
310      ESP_WS_CLIENT_MEM_CHECK(TAG, ws, goto _websocket_init_fail);
311  
312      esp_transport_set_default_port(ws, WEBSOCKET_TCP_DEFAULT_PORT);
313      esp_transport_list_add(client->transport_list, ws, "ws");
314      if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
315          asprintf(&client->config->scheme, "ws");
316          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
317      }
318  
319      esp_transport_handle_t ssl = esp_transport_ssl_init();
320      ESP_WS_CLIENT_MEM_CHECK(TAG, ssl, goto _websocket_init_fail);
321  
322      esp_transport_set_default_port(ssl, WEBSOCKET_SSL_DEFAULT_PORT);
323      if (config->cert_pem) {
324          esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem));
325      }
326      esp_transport_list_add(client->transport_list, ssl, "_ssl"); // need to save to transport list, for cleanup
327  
328      esp_transport_handle_t wss = esp_transport_ws_init(ssl);
329      ESP_WS_CLIENT_MEM_CHECK(TAG, wss, goto _websocket_init_fail);
330  
331      esp_transport_set_default_port(wss, WEBSOCKET_SSL_DEFAULT_PORT);
332  
333      esp_transport_list_add(client->transport_list, wss, "wss");
334      if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) {
335          asprintf(&client->config->scheme, "wss");
336          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
337      }
338  
339      if (config->uri) {
340          if (esp_websocket_client_set_uri(client, config->uri) != ESP_OK) {
341              ESP_LOGE(TAG, "Invalid uri");
342              goto _websocket_init_fail;
343          }
344      }
345  
346      if (esp_websocket_client_set_config(client, config) != ESP_OK) {
347          ESP_LOGE(TAG, "Failed to set the configuration");
348          goto _websocket_init_fail;
349      }
350  
351      if (client->config->scheme == NULL) {
352          asprintf(&client->config->scheme, "ws");
353          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
354      }
355  
356      set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "ws"));
357      set_websocket_transport_optional_settings(client, esp_transport_list_get_transport(client->transport_list, "wss"));
358  
359      client->keepalive_tick_ms = _tick_get_ms();
360      client->reconnect_tick_ms = _tick_get_ms();
361      client->ping_tick_ms = _tick_get_ms();
362      client->wait_for_pong_resp = false;
363  
364      int buffer_size = config->buffer_size;
365      if (buffer_size <= 0) {
366          buffer_size = WEBSOCKET_BUFFER_SIZE_BYTE;
367      }
368      client->rx_buffer = malloc(buffer_size);
369      ESP_WS_CLIENT_MEM_CHECK(TAG, client->rx_buffer, {
370          goto _websocket_init_fail;
371      });
372      client->tx_buffer = malloc(buffer_size);
373      ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_buffer, {
374          goto _websocket_init_fail;
375      });
376      client->status_bits = xEventGroupCreate();
377      ESP_WS_CLIENT_MEM_CHECK(TAG, client->status_bits, {
378          goto _websocket_init_fail;
379      });
380  
381      client->buffer_size = buffer_size;
382      return client;
383  
384  _websocket_init_fail:
385      esp_websocket_client_destroy(client);
386      return NULL;
387  }
388  
389  esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
390  {
391      if (client == NULL) {
392          return ESP_ERR_INVALID_ARG;
393      }
394      if (client->run) {
395          esp_websocket_client_stop(client);
396      }
397      if (client->event_handle) {
398          esp_event_loop_delete(client->event_handle);
399      }
400      esp_websocket_client_destroy_config(client);
401      esp_transport_list_destroy(client->transport_list);
402      vQueueDelete(client->lock);
403      free(client->tx_buffer);
404      free(client->rx_buffer);
405      if (client->status_bits) {
406          vEventGroupDelete(client->status_bits);
407      }
408      free(client);
409      client = NULL;
410      return ESP_OK;
411  }
412  
413  esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, const char *uri)
414  {
415      if (client == NULL || uri == NULL) {
416          return ESP_ERR_INVALID_ARG;
417      }
418      struct http_parser_url puri;
419      http_parser_url_init(&puri);
420      int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri);
421      if (parser_status != 0) {
422          ESP_LOGE(TAG, "Error parse uri = %s", uri);
423          return ESP_FAIL;
424      }
425      if (puri.field_data[UF_SCHEMA].len) {
426          free(client->config->scheme);
427          asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off);
428          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM);
429      }
430  
431      if (puri.field_data[UF_HOST].len) {
432          free(client->config->host);
433          asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off);
434          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM);
435      }
436  
437  
438      if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) {
439          free(client->config->path);
440          if (puri.field_data[UF_QUERY].len == 0) {
441              asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
442          } else if (puri.field_data[UF_PATH].len == 0)  {
443              asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
444          } else {
445              asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
446                      puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
447          }
448          ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM);
449      }
450      if (puri.field_data[UF_PORT].off) {
451          client->config->port = strtol((const char*)(uri + puri.field_data[UF_PORT].off), NULL, 10);
452      }
453  
454      if (puri.field_data[UF_USERINFO].len) {
455          char *user_info = NULL;
456          asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off);
457          if (user_info) {
458              char *pass = strchr(user_info, ':');
459              if (pass) {
460                  pass[0] = 0; //terminal username
461                  pass ++;
462                  free(client->config->password);
463                  client->config->password = strdup(pass);
464                  ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->password, return ESP_ERR_NO_MEM);
465              }
466              free(client->config->username);
467              client->config->username = strdup(user_info);
468              ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->username, return ESP_ERR_NO_MEM);
469              free(user_info);
470          } else {
471              return ESP_ERR_NO_MEM;
472          }
473      }
474      return ESP_OK;
475  }
476  
477  static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
478  {
479      int rlen;
480      client->payload_offset = 0;
481      do {
482          rlen = esp_transport_read(client->transport, client->rx_buffer, client->buffer_size, client->config->network_timeout_ms);
483          if (rlen < 0) {
484              ESP_LOGE(TAG, "Error read data");
485              return ESP_FAIL;
486          }
487          client->payload_len = esp_transport_ws_get_read_payload_len(client->transport);
488          client->last_opcode = esp_transport_ws_get_read_opcode(client->transport);
489  
490          esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_DATA, client->rx_buffer, rlen);
491  
492          client->payload_offset += rlen;
493      } while (client->payload_offset < client->payload_len);
494  
495      // if a PING message received -> send out the PONG, this will not work for PING messages with payload longer than buffer len
496      if (client->last_opcode == WS_TRANSPORT_OPCODES_PING) {
497          const char *data = (client->payload_len == 0) ? NULL : client->rx_buffer;
498          esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN, data, client->payload_len,
499                                    client->config->network_timeout_ms);
500      } else if (client->last_opcode == WS_TRANSPORT_OPCODES_PONG) {
501          client->wait_for_pong_resp = false;
502      } else if (client->last_opcode == WS_TRANSPORT_OPCODES_CLOSE) {
503          ESP_LOGD(TAG, "Received close frame");
504          client->state = WEBSOCKET_STATE_CLOSING;
505      }
506  
507      return ESP_OK;
508  }
509  
510  static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout);
511  
512  static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout);
513  
514  static void esp_websocket_client_task(void *pv)
515  {
516      const int lock_timeout = portMAX_DELAY;
517      esp_websocket_client_handle_t client = (esp_websocket_client_handle_t) pv;
518      client->run = true;
519  
520      //get transport by scheme
521      client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme);
522  
523      if (client->transport == NULL) {
524          ESP_LOGE(TAG, "There are no transports valid, stop websocket client");
525          client->run = false;
526      }
527      //default port
528      if (client->config->port == 0) {
529          client->config->port = esp_transport_get_default_port(client->transport);
530      }
531  
532      client->state = WEBSOCKET_STATE_INIT;
533      xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
534      int read_select = 0;
535      while (client->run) {
536          if (xSemaphoreTakeRecursive(client->lock, lock_timeout) != pdPASS) {
537              ESP_LOGE(TAG, "Failed to lock ws-client tasks, exitting the task...");
538              break;
539          }
540          switch ((int)client->state) {
541              case WEBSOCKET_STATE_INIT:
542                  if (client->transport == NULL) {
543                      ESP_LOGE(TAG, "There are no transport");
544                      client->run = false;
545                      break;
546                  }
547                  if (esp_transport_connect(client->transport,
548                                            client->config->host,
549                                            client->config->port,
550                                            client->config->network_timeout_ms) < 0) {
551                      ESP_LOGE(TAG, "Error transport connect");
552                      esp_websocket_client_abort_connection(client);
553                      break;
554                  }
555                  ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port);
556  
557                  client->state = WEBSOCKET_STATE_CONNECTED;
558                  client->wait_for_pong_resp = false;
559                  esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CONNECTED, NULL, 0);
560  
561                  break;
562              case WEBSOCKET_STATE_CONNECTED:
563                  if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) { // only send and check for PING
564                                                                                                            // if closing hasn't been initiated
565                      if (_tick_get_ms() - client->ping_tick_ms > WEBSOCKET_PING_TIMEOUT_MS) {
566                          client->ping_tick_ms = _tick_get_ms();
567                          ESP_LOGD(TAG, "Sending PING...");
568                          esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_PING | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
569  
570                          if (!client->wait_for_pong_resp && client->config->pingpong_timeout_sec) {
571                              client->pingpong_tick_ms = _tick_get_ms();
572                              client->wait_for_pong_resp = true;
573                          }
574                      }
575  
576                      if ( _tick_get_ms() - client->pingpong_tick_ms > client->config->pingpong_timeout_sec*1000 ) {
577                          if (client->wait_for_pong_resp) {
578                              ESP_LOGE(TAG, "Error, no PONG received for more than %d seconds after PING", client->config->pingpong_timeout_sec);
579                              esp_websocket_client_abort_connection(client);
580                              break;
581                          }
582                      }
583                  }
584  
585                  if (read_select == 0) {
586                      ESP_LOGV(TAG, "Read poll timeout: skipping esp_transport_read()...");
587                      break;
588                  }
589                  client->ping_tick_ms = _tick_get_ms();
590  
591                  if (esp_websocket_client_recv(client) == ESP_FAIL) {
592                      ESP_LOGE(TAG, "Error receive data");
593                      esp_websocket_client_abort_connection(client);
594                      break;
595                  }
596                  break;
597              case WEBSOCKET_STATE_WAIT_TIMEOUT:
598  
599                  if (!client->config->auto_reconnect) {
600                      client->run = false;
601                      break;
602                  }
603                  if (_tick_get_ms() - client->reconnect_tick_ms > client->wait_timeout_ms) {
604                      client->state = WEBSOCKET_STATE_INIT;
605                      client->reconnect_tick_ms = _tick_get_ms();
606                      ESP_LOGD(TAG, "Reconnecting...");
607                  }
608                  break;
609              case WEBSOCKET_STATE_CLOSING:
610                  // if closing not initiated by the client echo the close message back
611                  if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
612                      ESP_LOGD(TAG, "Closing initiated by the server, sending close frame");
613                      esp_transport_ws_send_raw(client->transport, WS_TRANSPORT_OPCODES_CLOSE | WS_TRANSPORT_OPCODES_FIN, NULL, 0, client->config->network_timeout_ms);
614                      xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
615                  }
616                  break;
617              default:
618                  ESP_LOGD(TAG, "Client run iteration in a default state: %d", client->state);
619                  break;
620          }
621          xSemaphoreGiveRecursive(client->lock);
622          if (WEBSOCKET_STATE_CONNECTED == client->state) {
623              read_select = esp_transport_poll_read(client->transport, 1000); //Poll every 1000ms
624              if (read_select < 0) {
625                  ESP_LOGE(TAG, "Network error: esp_transport_poll_read() returned %d, errno=%d", read_select, errno);
626                  esp_websocket_client_abort_connection(client);
627              }
628          } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
629              // waiting for reconnecting...
630              vTaskDelay(client->wait_timeout_ms / 2 / portTICK_RATE_MS);
631          } else if (WEBSOCKET_STATE_CLOSING == client->state &&
632                    (CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
633              ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
634              int ret = esp_transport_ws_poll_connection_closed(client->transport, 1000);
635              if (ret == 0) {
636                  // still waiting
637                  break;
638              }
639              if (ret < 0) {
640                  ESP_LOGW(TAG, "Connection terminated while waiting for clean TCP close");
641              }
642              client->run = false;
643              client->state = WEBSOCKET_STATE_UNKNOW;
644              esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_CLOSED, NULL, 0);
645              break;
646          }
647      }
648  
649      esp_transport_close(client->transport);
650      xEventGroupSetBits(client->status_bits, STOPPED_BIT);
651      client->state = WEBSOCKET_STATE_UNKNOW;
652      vTaskDelete(NULL);
653  }
654  
655  esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
656  {
657      if (client == NULL) {
658          return ESP_ERR_INVALID_ARG;
659      }
660      if (client->state >= WEBSOCKET_STATE_INIT) {
661          ESP_LOGE(TAG, "The client has started");
662          return ESP_FAIL;
663      }
664      if (xTaskCreate(esp_websocket_client_task, "websocket_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) {
665          ESP_LOGE(TAG, "Error create websocket task");
666          return ESP_FAIL;
667      }
668      xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
669      return ESP_OK;
670  }
671  
672  esp_err_t esp_websocket_client_stop(esp_websocket_client_handle_t client)
673  {
674      if (client == NULL) {
675          return ESP_ERR_INVALID_ARG;
676      }
677      if (!client->run) {
678          ESP_LOGW(TAG, "Client was not started");
679          return ESP_FAIL;
680      }
681  
682      /* A running client cannot be stopped from the websocket task/event handler */
683      TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
684      if (running_task == client->task_handle) {
685          ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
686          return ESP_FAIL;
687      }
688  
689  
690      client->run = false;
691      xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
692      client->state = WEBSOCKET_STATE_UNKNOW;
693      return ESP_OK;
694  }
695  
696  static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout)
697  {
698      uint8_t *close_status_data = NULL;
699      // RFC6455#section-5.5.1: The Close frame MAY contain a body (indicated by total_len >= 2)
700      if (total_len >= 2) {
701          close_status_data = calloc(1, total_len);
702          ESP_WS_CLIENT_MEM_CHECK(TAG, close_status_data, return -1);
703          // RFC6455#section-5.5.1: The first two bytes of the body MUST be a 2-byte representing a status
704          uint16_t *code_network_order = (uint16_t *) close_status_data;
705          *code_network_order = htons(code);
706          memcpy(close_status_data + 2, additional_data, total_len - 2);
707      }
708      int ret = esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_CLOSE, close_status_data, total_len, timeout);
709      free(close_status_data);
710      return ret;
711  }
712  
713  
714  static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_client_handle_t client, bool send_body, int code, const char *data, int len, TickType_t timeout)
715  {
716      if (client == NULL) {
717          return ESP_ERR_INVALID_ARG;
718      }
719      if (!client->run) {
720          ESP_LOGW(TAG, "Client was not started");
721          return ESP_FAIL;
722      }
723  
724      /* A running client cannot be stopped from the websocket task/event handler */
725      TaskHandle_t running_task = xTaskGetCurrentTaskHandle();
726      if (running_task == client->task_handle) {
727          ESP_LOGE(TAG, "Client cannot be stopped from websocket task");
728          return ESP_FAIL;
729      }
730  
731      if (send_body) {
732          esp_websocket_client_send_close(client, code, data, len + 2, portMAX_DELAY); // len + 2 -> always sending the code
733      } else {
734          esp_websocket_client_send_close(client, 0, NULL, 0, portMAX_DELAY); // only opcode frame
735      }
736  
737      // Set closing bit to prevent from sending PING frames while connected
738      xEventGroupSetBits(client->status_bits, CLOSE_FRAME_SENT_BIT);
739  
740      if (STOPPED_BIT & xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, timeout)) {
741          return ESP_OK;
742      }
743  
744      // If could not close gracefully within timeout, stop the client and disconnect
745      client->run = false;
746      xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
747      client->state = WEBSOCKET_STATE_UNKNOW;
748      return ESP_OK;
749  }
750  
751  esp_err_t esp_websocket_client_close_with_code(esp_websocket_client_handle_t client, int code, const char *data, int len, TickType_t timeout)
752  {
753      return esp_websocket_client_close_with_optional_body(client, true, code, data, len, timeout);
754  }
755  
756  esp_err_t esp_websocket_client_close(esp_websocket_client_handle_t client, TickType_t timeout)
757  {
758      return esp_websocket_client_close_with_optional_body(client, false, 0, NULL, 0, timeout);
759  }
760  
761  int esp_websocket_client_send_text(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
762  {
763      return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_TEXT, (const uint8_t *)data, len, timeout);
764  }
765  
766  int esp_websocket_client_send(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
767  {
768      return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
769  }
770  
771  int esp_websocket_client_send_bin(esp_websocket_client_handle_t client, const char *data, int len, TickType_t timeout)
772  {
773      return esp_websocket_client_send_with_opcode(client, WS_TRANSPORT_OPCODES_BINARY, (const uint8_t *)data, len, timeout);
774  }
775  
776  static int esp_websocket_client_send_with_opcode(esp_websocket_client_handle_t client, ws_transport_opcodes_t opcode, const uint8_t *data, int len, TickType_t timeout)
777  {
778      int need_write = len;
779      int wlen = 0, widx = 0;
780      int ret = ESP_FAIL;
781  
782      if (client == NULL || len < 0 ||
783          (opcode != WS_TRANSPORT_OPCODES_CLOSE && (data == NULL || len <= 0))) {
784          ESP_LOGE(TAG, "Invalid arguments");
785          return ESP_FAIL;
786      }
787  
788      if (xSemaphoreTakeRecursive(client->lock, timeout) != pdPASS) {
789          ESP_LOGE(TAG, "Could not lock ws-client within %d timeout", timeout);
790          return ESP_FAIL;
791      }
792  
793      if (!esp_websocket_client_is_connected(client)) {
794          ESP_LOGE(TAG, "Websocket client is not connected");
795          goto unlock_and_return;
796      }
797  
798      if (client->transport == NULL) {
799          ESP_LOGE(TAG, "Invalid transport");
800          goto unlock_and_return;
801      }
802      uint32_t current_opcode = opcode;
803      while (widx < len || current_opcode) {  // allow for sending "current_opcode" only message with len==0
804          if (need_write > client->buffer_size) {
805              need_write = client->buffer_size;
806          } else {
807              current_opcode |= WS_TRANSPORT_OPCODES_FIN;
808          }
809          memcpy(client->tx_buffer, data + widx, need_write);
810          // send with ws specific way and specific opcode
811          wlen = esp_transport_ws_send_raw(client->transport, current_opcode, (char *)client->tx_buffer, need_write,
812                                          (timeout==portMAX_DELAY)? -1 : timeout * portTICK_PERIOD_MS);
813          if (wlen < 0 || (wlen == 0 && need_write != 0)) {
814              ret = wlen;
815              ESP_LOGE(TAG, "Network error: esp_transport_write() returned %d, errno=%d", ret, errno);
816              esp_websocket_client_abort_connection(client);
817              goto unlock_and_return;
818          }
819          current_opcode = 0;
820          widx += wlen;
821          need_write = len - widx;
822  
823      }
824      ret = widx;
825  unlock_and_return:
826      xSemaphoreGiveRecursive(client->lock);
827      return ret;
828  }
829  
830  bool esp_websocket_client_is_connected(esp_websocket_client_handle_t client)
831  {
832      if (client == NULL) {
833          return false;
834      }
835      return client->state == WEBSOCKET_STATE_CONNECTED;
836  }
837  
838  esp_err_t esp_websocket_register_events(esp_websocket_client_handle_t client,
839                                          esp_websocket_event_id_t event,
840                                          esp_event_handler_t event_handler,
841                                          void *event_handler_arg)
842  {
843      if (client == NULL) {
844          return ESP_ERR_INVALID_ARG;
845      }
846      return esp_event_handler_register_with(client->event_handle, WEBSOCKET_EVENTS, event, event_handler, event_handler_arg);
847  }