| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /*
- * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include <stdlib.h>
- #include <esp_log.h>
- #include <esp_err.h>
- #include <fcntl.h>
- #include <errno.h>
- #include <unistd.h>
- #include <esp_http_server.h>
- #include "esp_httpd_priv.h"
- static const char *TAG = "httpd_sess";
- typedef enum {
- HTTPD_TASK_NONE = 0,
- HTTPD_TASK_INIT, // Init session
- HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1)
- HTTPD_TASK_GET_FREE, // Get free session slot (fd<0)
- HTTPD_TASK_FIND_FD, // Find session with specific fd
- HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor
- HTTPD_TASK_DELETE_INVALID, // Delete invalid session
- HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
- HTTPD_TASK_CLOSE // Close session
- } task_t;
- typedef struct {
- task_t task;
- int fd;
- fd_set *fdset;
- int max_fd;
- struct httpd_data *hd;
- uint64_t lru_counter;
- struct sock_db *session;
- } enum_context_t;
- void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
- {
- if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
- return;
- }
- struct sock_db *current = hd->hd_sd;
- struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
- while (current <= end) {
- if (enum_function && (!enum_function(current, context))) {
- break;
- }
- current++;
- }
- }
- // Check if a FD is valid
- static int fd_is_valid(int fd)
- {
- return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
- }
- static int enum_function(struct sock_db *session, void *context)
- {
- if ((!session) || (!context)) {
- return 0;
- }
- enum_context_t *ctx = (enum_context_t *) context;
- int found = 0;
- switch (ctx->task) {
- // Initialize session
- case HTTPD_TASK_INIT:
- session->fd = -1;
- session->ctx = NULL;
- break;
- // Get active session
- case HTTPD_TASK_GET_ACTIVE:
- found = (session->fd != -1);
- break;
- // Get free slot
- case HTTPD_TASK_GET_FREE:
- found = (session->fd < 0);
- break;
- // Find fd
- case HTTPD_TASK_FIND_FD:
- found = (session->fd == ctx->fd);
- break;
- // Set descriptor
- case HTTPD_TASK_SET_DESCRIPTOR:
- if (session->fd != -1) {
- FD_SET(session->fd, ctx->fdset);
- if (session->fd > ctx->max_fd) {
- ctx->max_fd = session->fd;
- }
- }
- break;
- // Delete invalid session
- case HTTPD_TASK_DELETE_INVALID:
- if (!fd_is_valid(session->fd)) {
- ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
- httpd_sess_delete(ctx->hd, session);
- }
- break;
- // Find lowest lru
- case HTTPD_TASK_FIND_LOWEST_LRU:
- // Found free slot - no need to check other sessions
- if (session->fd == -1) {
- return 0;
- }
- // Check/update lowest lru
- if (session->lru_counter < ctx->lru_counter) {
- ctx->lru_counter = session->lru_counter;
- ctx->session = session;
- }
- break;
- case HTTPD_TASK_CLOSE:
- if (session->fd != -1) {
- ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
- httpd_sess_delete(ctx->hd, session);
- }
- break;
- default:
- return 0;
- }
- if (found) {
- ctx->session = session;
- return 0;
- }
- return 1;
- }
- static void httpd_sess_close(void *arg)
- {
- struct sock_db *sock_db = (struct sock_db *) arg;
- if (!sock_db) {
- return;
- }
- if (!sock_db->lru_counter && !sock_db->lru_socket) {
- ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
- return;
- }
- sock_db->lru_socket = false;
- struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
- httpd_sess_delete(hd, sock_db);
- }
- struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
- {
- if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
- return NULL;
- }
- enum_context_t context = {
- .task = HTTPD_TASK_GET_FREE
- };
- httpd_sess_enum(hd, enum_function, &context);
- return context.session;
- }
- bool httpd_is_sess_available(struct httpd_data *hd)
- {
- return httpd_sess_get_free(hd) ? true : false;
- }
- struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
- {
- if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
- return NULL;
- }
- // Check if called inside a request handler, and the session sockfd in use is same as the parameter
- // => Just return the pointer to the sock_db corresponding to the request
- if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
- return hd->hd_req_aux.sd;
- }
- enum_context_t context = {
- .task = HTTPD_TASK_FIND_FD,
- .fd = sockfd
- };
- httpd_sess_enum(hd, enum_function, &context);
- return context.session;
- }
- esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
- {
- ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
- if (httpd_sess_get(hd, newfd)) {
- ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
- return ESP_FAIL;
- }
- struct sock_db *session = httpd_sess_get_free(hd);
- if (!session) {
- ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
- return ESP_FAIL;
- }
- // Clear session data
- memset(session, 0, sizeof (struct sock_db));
- session->fd = newfd;
- session->handle = (httpd_handle_t) hd;
- session->send_fn = httpd_default_send;
- session->recv_fn = httpd_default_recv;
- // Call user-defined session opening function
- if (hd->config.open_fn) {
- esp_err_t ret = hd->config.open_fn(hd, session->fd);
- if (ret != ESP_OK) {
- httpd_sess_delete(hd, session);
- ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
- return ret;
- }
- }
- // increment number of sessions
- hd->hd_sd_active_count++;
- ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
- return ESP_OK;
- }
- void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
- {
- if ((!ctx) || (!*ctx)) {
- return;
- }
- if (free_fn) {
- free_fn(*ctx);
- } else {
- free(*ctx);
- }
- *ctx = NULL;
- }
- void httpd_sess_clear_ctx(struct sock_db *session)
- {
- if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
- return;
- }
- // free user ctx
- if (session->ctx) {
- httpd_sess_free_ctx(&session->ctx, session->free_ctx);
- session->free_ctx = NULL;
- }
- // Free 'transport' context
- if (session->transport_ctx) {
- httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
- session->free_transport_ctx = NULL;
- }
- }
- void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
- {
- struct sock_db *session = httpd_sess_get(handle, sockfd);
- if (!session) {
- return NULL;
- }
- // Check if the function has been called from inside a
- // request handler, in which case fetch the context from
- // the httpd_req_t structure
- struct httpd_data *hd = (struct httpd_data *) handle;
- if (hd->hd_req_aux.sd == session) {
- return hd->hd_req.sess_ctx;
- }
- return session->ctx;
- }
- void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
- {
- struct sock_db *session = httpd_sess_get(handle, sockfd);
- if (!session) {
- return;
- }
- // Check if the function has been called from inside a
- // request handler, in which case set the context inside
- // the httpd_req_t structure
- struct httpd_data *hd = (struct httpd_data *) handle;
- if (hd->hd_req_aux.sd == session) {
- if (hd->hd_req.sess_ctx != ctx) {
- // Don't free previous context if it is in sockdb
- // as it will be freed inside httpd_req_cleanup()
- if (session->ctx != hd->hd_req.sess_ctx) {
- httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
- }
- hd->hd_req.sess_ctx = ctx;
- }
- hd->hd_req.free_ctx = free_fn;
- return;
- }
- // Else set the context inside the sock_db structure
- if (session->ctx != ctx) {
- // Free previous context
- httpd_sess_free_ctx(&session->ctx, session->free_ctx);
- session->ctx = ctx;
- }
- session->free_ctx = free_fn;
- }
- void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
- {
- struct sock_db *session = httpd_sess_get(handle, sockfd);
- return session ? session->transport_ctx : NULL;
- }
- void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
- {
- struct sock_db *session = httpd_sess_get(handle, sockfd);
- if (!session) {
- return;
- }
- if (session->transport_ctx != ctx) {
- // Free previous transport context
- httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
- session->transport_ctx = ctx;
- }
- session->free_transport_ctx = free_fn;
- }
- void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
- {
- enum_context_t context = {
- .task = HTTPD_TASK_SET_DESCRIPTOR,
- .max_fd = -1,
- .fdset = fdset
- };
- httpd_sess_enum(hd, enum_function, &context);
- if (maxfd) {
- *maxfd = context.max_fd;
- }
- }
- void httpd_sess_delete_invalid(struct httpd_data *hd)
- {
- enum_context_t context = {
- .task = HTTPD_TASK_DELETE_INVALID,
- .hd = hd
- };
- httpd_sess_enum(hd, enum_function, &context);
- }
- void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
- {
- if ((!hd) || (!session) || (session->fd < 0)) {
- return;
- }
- ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
- // Call close function if defined
- if (hd->config.close_fn) {
- hd->config.close_fn(hd, session->fd);
- } else {
- close(session->fd);
- }
- // clear all contexts
- httpd_sess_clear_ctx(session);
- // mark session slot as available
- session->fd = -1;
- // decrement number of sessions
- hd->hd_sd_active_count--;
- ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
- if (!hd->hd_sd_active_count) {
- hd->lru_counter = 0;
- }
- }
- void httpd_sess_init(struct httpd_data *hd)
- {
- enum_context_t context = {
- .task = HTTPD_TASK_INIT
- };
- httpd_sess_enum(hd, enum_function, &context);
- }
- bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
- {
- if (!session) {
- return false;
- }
- if (session->pending_fn) {
- // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
- // this should check e.g. for the SSL data buffer
- if (session->pending_fn(hd, session->fd) > 0) {
- return true;
- }
- }
- return (session->pending_len != 0);
- }
- /* This MUST return ESP_OK on successful execution. If any other
- * value is returned, everything related to this socket will be
- * cleaned up and the socket will be closed.
- */
- esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
- {
- if ((!hd) || (!session)) {
- return ESP_FAIL;
- }
- ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
- if (httpd_req_new(hd, session) != ESP_OK) {
- return ESP_FAIL;
- }
- ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
- if (httpd_req_delete(hd) != ESP_OK) {
- return ESP_FAIL;
- }
- ESP_LOGD(TAG, LOG_FMT("success"));
- session->lru_counter = ++hd->lru_counter;
- return ESP_OK;
- }
- esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
- {
- if (handle == NULL) {
- return ESP_ERR_INVALID_ARG;
- }
- struct httpd_data *hd = (struct httpd_data *) handle;
- enum_context_t context = {
- .task = HTTPD_TASK_FIND_FD,
- .fd = sockfd
- };
- httpd_sess_enum(hd, enum_function, &context);
- if (context.session) {
- context.session->lru_counter = ++hd->lru_counter;
- return ESP_OK;
- }
- return ESP_ERR_NOT_FOUND;
- }
- esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
- {
- enum_context_t context = {
- .task = HTTPD_TASK_FIND_LOWEST_LRU,
- .lru_counter = UINT64_MAX,
- .fd = -1
- };
- httpd_sess_enum(hd, enum_function, &context);
- if (!context.session) {
- return ESP_OK;
- }
- ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
- context.session->lru_socket = true;
- return httpd_sess_trigger_close_(hd, context.session);
- }
- esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
- {
- if (!session) {
- return ESP_ERR_NOT_FOUND;
- }
- return httpd_queue_work(handle, httpd_sess_close, session);
- }
- esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
- {
- struct sock_db *session = httpd_sess_get(handle, sockfd);
- if (!session) {
- return ESP_ERR_NOT_FOUND;
- }
- return httpd_sess_trigger_close_(handle, session);
- }
- void httpd_sess_close_all(struct httpd_data *hd)
- {
- enum_context_t context = {
- .task = HTTPD_TASK_CLOSE,
- .hd = hd
- };
- httpd_sess_enum(hd, enum_function, &context);
- }
|