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 }