/ components / esp_http_server / src / httpd_sess.c
httpd_sess.c
  1  // Copyright 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  
 16  #include <stdlib.h>
 17  #include <esp_log.h>
 18  #include <esp_err.h>
 19  
 20  #include <esp_http_server.h>
 21  #include "esp_httpd_priv.h"
 22  
 23  static const char *TAG = "httpd_sess";
 24  
 25  bool httpd_is_sess_available(struct httpd_data *hd)
 26  {
 27      int i;
 28      for (i = 0; i < hd->config.max_open_sockets; i++) {
 29          if (hd->hd_sd[i].fd == -1) {
 30              return true;
 31          }
 32      }
 33      return false;
 34  }
 35  
 36  struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
 37  {
 38      if (hd == NULL) {
 39          return NULL;
 40      }
 41  
 42      /* Check if called inside a request handler, and the
 43       * session sockfd in use is same as the parameter */
 44      if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
 45          /* Just return the pointer to the sock_db
 46           * corresponding to the request */
 47          return hd->hd_req_aux.sd;
 48      }
 49  
 50      int i;
 51      for (i = 0; i < hd->config.max_open_sockets; i++) {
 52          if (hd->hd_sd[i].fd == sockfd) {
 53              return &hd->hd_sd[i];
 54          }
 55      }
 56      return NULL;
 57  }
 58  
 59  esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
 60  {
 61      ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
 62  
 63      if (httpd_sess_get(hd, newfd)) {
 64          ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
 65          return ESP_FAIL;
 66      }
 67  
 68      int i;
 69      for (i = 0; i < hd->config.max_open_sockets; i++) {
 70          if (hd->hd_sd[i].fd == -1) {
 71              memset(&hd->hd_sd[i], 0, sizeof(hd->hd_sd[i]));
 72              hd->hd_sd[i].fd = newfd;
 73              hd->hd_sd[i].handle = (httpd_handle_t) hd;
 74              hd->hd_sd[i].send_fn = httpd_default_send;
 75              hd->hd_sd[i].recv_fn = httpd_default_recv;
 76  
 77              /* Call user-defined session opening function */
 78              if (hd->config.open_fn) {
 79                  esp_err_t ret = hd->config.open_fn(hd, hd->hd_sd[i].fd);
 80                  if (ret != ESP_OK) {
 81                      httpd_sess_delete(hd, hd->hd_sd[i].fd);
 82                      ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
 83                      return ret;
 84                  }
 85              }
 86              return ESP_OK;
 87          }
 88      }
 89      ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
 90      return ESP_FAIL;
 91  }
 92  
 93  void httpd_sess_free_ctx(void *ctx, httpd_free_ctx_fn_t free_fn)
 94  {
 95      if (ctx) {
 96          if (free_fn) {
 97              free_fn(ctx);
 98          } else {
 99              free(ctx);
100          }
101      }
102  }
103  
104  void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
105  {
106      struct sock_db *sd = httpd_sess_get(handle, sockfd);
107      if (sd == NULL) {
108          return NULL;
109      }
110  
111      /* Check if the function has been called from inside a
112       * request handler, in which case fetch the context from
113       * the httpd_req_t structure */
114      struct httpd_data *hd = (struct httpd_data *) handle;
115      if (hd->hd_req_aux.sd == sd) {
116          return hd->hd_req.sess_ctx;
117      }
118  
119      return sd->ctx;
120  }
121  
122  void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
123  {
124      struct sock_db *sd = httpd_sess_get(handle, sockfd);
125      if (sd == NULL) {
126          return;
127      }
128  
129      /* Check if the function has been called from inside a
130       * request handler, in which case set the context inside
131       * the httpd_req_t structure */
132      struct httpd_data *hd = (struct httpd_data *) handle;
133      if (hd->hd_req_aux.sd == sd) {
134          if (hd->hd_req.sess_ctx != ctx) {
135              /* Don't free previous context if it is in sockdb
136               * as it will be freed inside httpd_req_cleanup() */
137              if (sd->ctx != hd->hd_req.sess_ctx) {
138                  /* Free previous context */
139                  httpd_sess_free_ctx(hd->hd_req.sess_ctx, hd->hd_req.free_ctx);
140              }
141              hd->hd_req.sess_ctx = ctx;
142          }
143          hd->hd_req.free_ctx = free_fn;
144          return;
145      }
146  
147      /* Else set the context inside the sock_db structure */
148      if (sd->ctx != ctx) {
149          /* Free previous context */
150          httpd_sess_free_ctx(sd->ctx, sd->free_ctx);
151          sd->ctx = ctx;
152      }
153      sd->free_ctx = free_fn;
154  }
155  
156  void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
157  {
158      struct sock_db *sd = httpd_sess_get(handle, sockfd);
159      if (sd == NULL) {
160          return NULL;
161      }
162  
163      return sd->transport_ctx;
164  }
165  
166  void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
167  {
168      struct sock_db *sd = httpd_sess_get(handle, sockfd);
169      if (sd == NULL) {
170          return;
171      }
172  
173      if (sd->transport_ctx != ctx) {
174          /* Free previous transport context */
175          httpd_sess_free_ctx(sd->transport_ctx, sd->free_transport_ctx);
176          sd->transport_ctx = ctx;
177      }
178      sd->free_transport_ctx = free_fn;
179  }
180  
181  void httpd_sess_set_descriptors(struct httpd_data *hd,
182                                  fd_set *fdset, int *maxfd)
183  {
184      int i;
185      *maxfd = -1;
186      for (i = 0; i < hd->config.max_open_sockets; i++) {
187          if (hd->hd_sd[i].fd != -1) {
188              FD_SET(hd->hd_sd[i].fd, fdset);
189              if (hd->hd_sd[i].fd > *maxfd) {
190                  *maxfd = hd->hd_sd[i].fd;
191              }
192          }
193      }
194  }
195  
196  /** Check if a FD is valid */
197  static int fd_is_valid(int fd)
198  {
199      return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
200  }
201  
202  static inline uint64_t httpd_sess_get_lru_counter(void)
203  {
204      static uint64_t lru_counter = 0;
205      return ++lru_counter;
206  }
207  
208  void httpd_sess_delete_invalid(struct httpd_data *hd)
209  {
210      for (int i = 0; i < hd->config.max_open_sockets; i++) {
211          if (hd->hd_sd[i].fd != -1 && !fd_is_valid(hd->hd_sd[i].fd)) {
212              ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), hd->hd_sd[i].fd);
213              httpd_sess_delete(hd, hd->hd_sd[i].fd);
214          }
215      }
216  }
217  
218  int httpd_sess_delete(struct httpd_data *hd, int fd)
219  {
220      ESP_LOGD(TAG, LOG_FMT("fd = %d"), fd);
221      int i;
222      int pre_sess_fd = -1;
223      for (i = 0; i < hd->config.max_open_sockets; i++) {
224          if (hd->hd_sd[i].fd == fd) {
225              /* global close handler */
226              if (hd->config.close_fn) {
227                  hd->config.close_fn(hd, fd);
228              }
229  
230              /* release 'user' context */
231              if (hd->hd_sd[i].ctx) {
232                  if (hd->hd_sd[i].free_ctx) {
233                      hd->hd_sd[i].free_ctx(hd->hd_sd[i].ctx);
234                  } else {
235                      free(hd->hd_sd[i].ctx);
236                  }
237                  hd->hd_sd[i].ctx = NULL;
238                  hd->hd_sd[i].free_ctx = NULL;
239              }
240  
241              /* release 'transport' context */
242              if (hd->hd_sd[i].transport_ctx) {
243                  if (hd->hd_sd[i].free_transport_ctx) {
244                      hd->hd_sd[i].free_transport_ctx(hd->hd_sd[i].transport_ctx);
245                  } else {
246                      free(hd->hd_sd[i].transport_ctx);
247                  }
248                  hd->hd_sd[i].transport_ctx = NULL;
249                  hd->hd_sd[i].free_transport_ctx = NULL;
250              }
251  
252              /* mark session slot as available */
253              hd->hd_sd[i].fd = -1;
254              break;
255          } else if (hd->hd_sd[i].fd != -1) {
256              /* Return the fd just preceding the one being
257               * deleted so that iterator can continue from
258               * the correct fd */
259              pre_sess_fd = hd->hd_sd[i].fd;
260          }
261      }
262      return pre_sess_fd;
263  }
264  
265  void httpd_sess_init(struct httpd_data *hd)
266  {
267      int i;
268      for (i = 0; i < hd->config.max_open_sockets; i++) {
269          hd->hd_sd[i].fd = -1;
270          hd->hd_sd[i].ctx = NULL;
271      }
272  }
273  
274  bool httpd_sess_pending(struct httpd_data *hd, int fd)
275  {
276      struct sock_db *sd = httpd_sess_get(hd, fd);
277      if (! sd) {
278          return ESP_FAIL;
279      }
280  
281      if (sd->pending_fn) {
282          // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
283          // this should check e.g. for the SSL data buffer
284          if (sd->pending_fn(hd, fd) > 0) {
285              return true;
286          }
287      }
288  
289      return (sd->pending_len != 0);
290  }
291  
292  /* This MUST return ESP_OK on successful execution. If any other
293   * value is returned, everything related to this socket will be
294   * cleaned up and the socket will be closed.
295   */
296  esp_err_t httpd_sess_process(struct httpd_data *hd, int newfd)
297  {
298      struct sock_db *sd = httpd_sess_get(hd, newfd);
299      if (! sd) {
300          return ESP_FAIL;
301      }
302  
303      ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
304      if (httpd_req_new(hd, sd) != ESP_OK) {
305          return ESP_FAIL;
306      }
307      ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
308      if (httpd_req_delete(hd) != ESP_OK) {
309          return ESP_FAIL;
310      }
311      ESP_LOGD(TAG, LOG_FMT("success"));
312      sd->lru_counter = httpd_sess_get_lru_counter();
313      return ESP_OK;
314  }
315  
316  esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
317  {
318      if (handle == NULL) {
319          return ESP_ERR_INVALID_ARG;
320      }
321  
322      /* Search for the socket database entry */
323      struct httpd_data *hd = (struct httpd_data *) handle;
324      int i;
325      for (i = 0; i < hd->config.max_open_sockets; i++) {
326          if (hd->hd_sd[i].fd == sockfd) {
327              hd->hd_sd[i].lru_counter = httpd_sess_get_lru_counter();
328              return ESP_OK;
329          }
330      }
331      return ESP_ERR_NOT_FOUND;
332  }
333  
334  esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
335  {
336      uint64_t lru_counter = UINT64_MAX;
337      int lru_fd = -1;
338      int i;
339      for (i = 0; i < hd->config.max_open_sockets; i++) {
340          /* If a descriptor is -1, there is no need to close any session.
341           * So, we can return from here, without finding the Least Recently Used
342           * session
343           */
344          if (hd->hd_sd[i].fd == -1) {
345              return ESP_OK;
346          }
347          if (hd->hd_sd[i].lru_counter < lru_counter) {
348              lru_counter = hd->hd_sd[i].lru_counter;
349              lru_fd = hd->hd_sd[i].fd;
350          }
351      }
352      ESP_LOGD(TAG, LOG_FMT("fd = %d"), lru_fd);
353      return httpd_sess_trigger_close(hd, lru_fd);
354  }
355  
356  int httpd_sess_iterate(struct httpd_data *hd, int start_fd)
357  {
358      int start_index = 0;
359      int i;
360  
361      if (start_fd != -1) {
362          /* Take our index to where this fd is stored */
363          for (i = 0; i < hd->config.max_open_sockets; i++) {
364              if (hd->hd_sd[i].fd == start_fd) {
365                  start_index = i + 1;
366                  break;
367              }
368          }
369      }
370  
371      for (i = start_index; i < hd->config.max_open_sockets; i++) {
372          if (hd->hd_sd[i].fd != -1) {
373              return hd->hd_sd[i].fd;
374          }
375      }
376      return -1;
377  }
378  
379  static void httpd_sess_close(void *arg)
380  {
381      struct sock_db *sock_db = (struct sock_db *)arg;
382      if (sock_db) {
383          if (sock_db->lru_counter == 0) {
384              ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
385              return;
386          }
387          int fd = sock_db->fd;
388          struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
389          httpd_sess_delete(hd, fd);
390          close(fd);
391      }
392  }
393  
394  esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
395  {
396      struct sock_db *sock_db = httpd_sess_get(handle, sockfd);
397      if (sock_db) {
398          return httpd_queue_work(handle, httpd_sess_close, sock_db);
399      }
400  
401      return ESP_ERR_NOT_FOUND;
402  }