1 /*
2 * SPDX-FileCopyrightText: 2018-2021 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include <stdlib.h>
7 #include <esp_log.h>
8 #include <esp_err.h>
9 #include <fcntl.h>
10 #include <errno.h>
11 #include <unistd.h>
12
13 #include <esp_http_server.h>
14 #include "esp_httpd_priv.h"
15
16 static const char *TAG = "httpd_sess";
17
18 typedef enum {
19 HTTPD_TASK_NONE = 0,
20 HTTPD_TASK_INIT, // Init session
21 HTTPD_TASK_GET_ACTIVE, // Get active session (fd!=-1)
22 HTTPD_TASK_GET_FREE, // Get free session slot (fd<0)
23 HTTPD_TASK_FIND_FD, // Find session with specific fd
24 HTTPD_TASK_SET_DESCRIPTOR, // Set descriptor
25 HTTPD_TASK_DELETE_INVALID, // Delete invalid session
26 HTTPD_TASK_FIND_LOWEST_LRU, // Find session with lowest lru
27 HTTPD_TASK_CLOSE // Close session
28 } task_t;
29
30 typedef struct {
31 task_t task;
32 int fd;
33 fd_set *fdset;
34 int max_fd;
35 struct httpd_data *hd;
36 uint64_t lru_counter;
37 struct sock_db *session;
38 } enum_context_t;
39
httpd_sess_enum(struct httpd_data * hd,httpd_session_enum_function enum_function,void * context)40 void httpd_sess_enum(struct httpd_data *hd, httpd_session_enum_function enum_function, void *context)
41 {
42 if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
43 return;
44 }
45
46 struct sock_db *current = hd->hd_sd;
47 struct sock_db *end = hd->hd_sd + hd->config.max_open_sockets - 1;
48
49 while (current <= end) {
50 if (enum_function && (!enum_function(current, context))) {
51 break;
52 }
53 current++;
54 }
55 }
56
57 // Check if a FD is valid
fd_is_valid(int fd)58 static int fd_is_valid(int fd)
59 {
60 return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
61 }
62
enum_function(struct sock_db * session,void * context)63 static int enum_function(struct sock_db *session, void *context)
64 {
65 if ((!session) || (!context)) {
66 return 0;
67 }
68 enum_context_t *ctx = (enum_context_t *) context;
69 int found = 0;
70 switch (ctx->task) {
71 // Initialize session
72 case HTTPD_TASK_INIT:
73 session->fd = -1;
74 session->ctx = NULL;
75 break;
76 // Get active session
77 case HTTPD_TASK_GET_ACTIVE:
78 found = (session->fd != -1);
79 break;
80 // Get free slot
81 case HTTPD_TASK_GET_FREE:
82 found = (session->fd < 0);
83 break;
84 // Find fd
85 case HTTPD_TASK_FIND_FD:
86 found = (session->fd == ctx->fd);
87 break;
88 // Set descriptor
89 case HTTPD_TASK_SET_DESCRIPTOR:
90 if (session->fd != -1) {
91 FD_SET(session->fd, ctx->fdset);
92 if (session->fd > ctx->max_fd) {
93 ctx->max_fd = session->fd;
94 }
95 }
96 break;
97 // Delete invalid session
98 case HTTPD_TASK_DELETE_INVALID:
99 if (!fd_is_valid(session->fd)) {
100 ESP_LOGW(TAG, LOG_FMT("Closing invalid socket %d"), session->fd);
101 httpd_sess_delete(ctx->hd, session);
102 }
103 break;
104 // Find lowest lru
105 case HTTPD_TASK_FIND_LOWEST_LRU:
106 // Found free slot - no need to check other sessions
107 if (session->fd == -1) {
108 return 0;
109 }
110 // Check/update lowest lru
111 if (session->lru_counter < ctx->lru_counter) {
112 ctx->lru_counter = session->lru_counter;
113 ctx->session = session;
114 }
115 break;
116 case HTTPD_TASK_CLOSE:
117 if (session->fd != -1) {
118 ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), session->fd);
119 httpd_sess_delete(ctx->hd, session);
120 }
121 break;
122 default:
123 return 0;
124 }
125 if (found) {
126 ctx->session = session;
127 return 0;
128 }
129 return 1;
130 }
131
httpd_sess_close(void * arg)132 static void httpd_sess_close(void *arg)
133 {
134 struct sock_db *sock_db = (struct sock_db *) arg;
135 if (!sock_db) {
136 return;
137 }
138
139 if (!sock_db->lru_counter && !sock_db->lru_socket) {
140 ESP_LOGD(TAG, "Skipping session close for %d as it seems to be a race condition", sock_db->fd);
141 return;
142 }
143 sock_db->lru_socket = false;
144 struct httpd_data *hd = (struct httpd_data *) sock_db->handle;
145 httpd_sess_delete(hd, sock_db);
146 }
147
httpd_sess_get_free(struct httpd_data * hd)148 struct sock_db *httpd_sess_get_free(struct httpd_data *hd)
149 {
150 if ((!hd) || (hd->hd_sd_active_count == hd->config.max_open_sockets)) {
151 return NULL;
152 }
153 enum_context_t context = {
154 .task = HTTPD_TASK_GET_FREE
155 };
156 httpd_sess_enum(hd, enum_function, &context);
157 return context.session;
158 }
159
httpd_is_sess_available(struct httpd_data * hd)160 bool httpd_is_sess_available(struct httpd_data *hd)
161 {
162 return httpd_sess_get_free(hd) ? true : false;
163 }
164
httpd_sess_get(struct httpd_data * hd,int sockfd)165 struct sock_db *httpd_sess_get(struct httpd_data *hd, int sockfd)
166 {
167 if ((!hd) || (!hd->hd_sd) || (!hd->config.max_open_sockets)) {
168 return NULL;
169 }
170
171 // Check if called inside a request handler, and the session sockfd in use is same as the parameter
172 // => Just return the pointer to the sock_db corresponding to the request
173 if ((hd->hd_req_aux.sd) && (hd->hd_req_aux.sd->fd == sockfd)) {
174 return hd->hd_req_aux.sd;
175 }
176
177 enum_context_t context = {
178 .task = HTTPD_TASK_FIND_FD,
179 .fd = sockfd
180 };
181 httpd_sess_enum(hd, enum_function, &context);
182 return context.session;
183 }
184
httpd_sess_new(struct httpd_data * hd,int newfd)185 esp_err_t httpd_sess_new(struct httpd_data *hd, int newfd)
186 {
187 ESP_LOGD(TAG, LOG_FMT("fd = %d"), newfd);
188
189 if (httpd_sess_get(hd, newfd)) {
190 ESP_LOGE(TAG, LOG_FMT("session already exists with fd = %d"), newfd);
191 return ESP_FAIL;
192 }
193
194 struct sock_db *session = httpd_sess_get_free(hd);
195 if (!session) {
196 ESP_LOGD(TAG, LOG_FMT("unable to launch session for fd = %d"), newfd);
197 return ESP_FAIL;
198 }
199
200 // Clear session data
201 memset(session, 0, sizeof (struct sock_db));
202 session->fd = newfd;
203 session->handle = (httpd_handle_t) hd;
204 session->send_fn = httpd_default_send;
205 session->recv_fn = httpd_default_recv;
206
207 // Call user-defined session opening function
208 if (hd->config.open_fn) {
209 esp_err_t ret = hd->config.open_fn(hd, session->fd);
210 if (ret != ESP_OK) {
211 httpd_sess_delete(hd, session);
212 ESP_LOGD(TAG, LOG_FMT("open_fn failed for fd = %d"), newfd);
213 return ret;
214 }
215 }
216
217 // increment number of sessions
218 hd->hd_sd_active_count++;
219 ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
220
221 return ESP_OK;
222 }
223
httpd_sess_free_ctx(void ** ctx,httpd_free_ctx_fn_t free_fn)224 void httpd_sess_free_ctx(void **ctx, httpd_free_ctx_fn_t free_fn)
225 {
226 if ((!ctx) || (!*ctx)) {
227 return;
228 }
229 if (free_fn) {
230 free_fn(*ctx);
231 } else {
232 free(*ctx);
233 }
234 *ctx = NULL;
235 }
236
httpd_sess_clear_ctx(struct sock_db * session)237 void httpd_sess_clear_ctx(struct sock_db *session)
238 {
239 if ((!session) || ((!session->ctx) && (!session->transport_ctx))) {
240 return;
241 }
242
243 // free user ctx
244 if (session->ctx) {
245 httpd_sess_free_ctx(&session->ctx, session->free_ctx);
246 session->free_ctx = NULL;
247 }
248
249 // Free 'transport' context
250 if (session->transport_ctx) {
251 httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
252 session->free_transport_ctx = NULL;
253 }
254 }
255
httpd_sess_get_ctx(httpd_handle_t handle,int sockfd)256 void *httpd_sess_get_ctx(httpd_handle_t handle, int sockfd)
257 {
258 struct sock_db *session = httpd_sess_get(handle, sockfd);
259 if (!session) {
260 return NULL;
261 }
262
263 // Check if the function has been called from inside a
264 // request handler, in which case fetch the context from
265 // the httpd_req_t structure
266 struct httpd_data *hd = (struct httpd_data *) handle;
267 if (hd->hd_req_aux.sd == session) {
268 return hd->hd_req.sess_ctx;
269 }
270 return session->ctx;
271 }
272
httpd_sess_set_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)273 void httpd_sess_set_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
274 {
275 struct sock_db *session = httpd_sess_get(handle, sockfd);
276 if (!session) {
277 return;
278 }
279
280 // Check if the function has been called from inside a
281 // request handler, in which case set the context inside
282 // the httpd_req_t structure
283 struct httpd_data *hd = (struct httpd_data *) handle;
284 if (hd->hd_req_aux.sd == session) {
285 if (hd->hd_req.sess_ctx != ctx) {
286 // Don't free previous context if it is in sockdb
287 // as it will be freed inside httpd_req_cleanup()
288 if (session->ctx != hd->hd_req.sess_ctx) {
289 httpd_sess_free_ctx(&hd->hd_req.sess_ctx, hd->hd_req.free_ctx); // Free previous context
290 }
291 hd->hd_req.sess_ctx = ctx;
292 }
293 hd->hd_req.free_ctx = free_fn;
294 return;
295 }
296
297 // Else set the context inside the sock_db structure
298 if (session->ctx != ctx) {
299 // Free previous context
300 httpd_sess_free_ctx(&session->ctx, session->free_ctx);
301 session->ctx = ctx;
302 }
303 session->free_ctx = free_fn;
304 }
305
httpd_sess_get_transport_ctx(httpd_handle_t handle,int sockfd)306 void *httpd_sess_get_transport_ctx(httpd_handle_t handle, int sockfd)
307 {
308 struct sock_db *session = httpd_sess_get(handle, sockfd);
309 return session ? session->transport_ctx : NULL;
310 }
311
httpd_sess_set_transport_ctx(httpd_handle_t handle,int sockfd,void * ctx,httpd_free_ctx_fn_t free_fn)312 void httpd_sess_set_transport_ctx(httpd_handle_t handle, int sockfd, void *ctx, httpd_free_ctx_fn_t free_fn)
313 {
314 struct sock_db *session = httpd_sess_get(handle, sockfd);
315 if (!session) {
316 return;
317 }
318
319 if (session->transport_ctx != ctx) {
320 // Free previous transport context
321 httpd_sess_free_ctx(&session->transport_ctx, session->free_transport_ctx);
322 session->transport_ctx = ctx;
323 }
324 session->free_transport_ctx = free_fn;
325 }
326
httpd_sess_set_descriptors(struct httpd_data * hd,fd_set * fdset,int * maxfd)327 void httpd_sess_set_descriptors(struct httpd_data *hd, fd_set *fdset, int *maxfd)
328 {
329 enum_context_t context = {
330 .task = HTTPD_TASK_SET_DESCRIPTOR,
331 .max_fd = -1,
332 .fdset = fdset
333 };
334 httpd_sess_enum(hd, enum_function, &context);
335 if (maxfd) {
336 *maxfd = context.max_fd;
337 }
338 }
339
httpd_sess_delete_invalid(struct httpd_data * hd)340 void httpd_sess_delete_invalid(struct httpd_data *hd)
341 {
342 enum_context_t context = {
343 .task = HTTPD_TASK_DELETE_INVALID,
344 .hd = hd
345 };
346 httpd_sess_enum(hd, enum_function, &context);
347 }
348
httpd_sess_delete(struct httpd_data * hd,struct sock_db * session)349 void httpd_sess_delete(struct httpd_data *hd, struct sock_db *session)
350 {
351 if ((!hd) || (!session) || (session->fd < 0)) {
352 return;
353 }
354
355 ESP_LOGD(TAG, LOG_FMT("fd = %d"), session->fd);
356
357 // Call close function if defined
358 if (hd->config.close_fn) {
359 hd->config.close_fn(hd, session->fd);
360 } else {
361 close(session->fd);
362 }
363
364 // clear all contexts
365 httpd_sess_clear_ctx(session);
366
367 // mark session slot as available
368 session->fd = -1;
369
370 // decrement number of sessions
371 hd->hd_sd_active_count--;
372 ESP_LOGD(TAG, LOG_FMT("active sockets: %d"), hd->hd_sd_active_count);
373 if (!hd->hd_sd_active_count) {
374 hd->lru_counter = 0;
375 }
376 }
377
httpd_sess_init(struct httpd_data * hd)378 void httpd_sess_init(struct httpd_data *hd)
379 {
380 enum_context_t context = {
381 .task = HTTPD_TASK_INIT
382 };
383 httpd_sess_enum(hd, enum_function, &context);
384 }
385
httpd_sess_pending(struct httpd_data * hd,struct sock_db * session)386 bool httpd_sess_pending(struct httpd_data *hd, struct sock_db *session)
387 {
388 if (!session) {
389 return false;
390 }
391 if (session->pending_fn) {
392 // test if there's any data to be read (besides read() function, which is handled by select() in the main httpd loop)
393 // this should check e.g. for the SSL data buffer
394 if (session->pending_fn(hd, session->fd) > 0) {
395 return true;
396 }
397 }
398 return (session->pending_len != 0);
399 }
400
401 /* This MUST return ESP_OK on successful execution. If any other
402 * value is returned, everything related to this socket will be
403 * cleaned up and the socket will be closed.
404 */
httpd_sess_process(struct httpd_data * hd,struct sock_db * session)405 esp_err_t httpd_sess_process(struct httpd_data *hd, struct sock_db *session)
406 {
407 if ((!hd) || (!session)) {
408 return ESP_FAIL;
409 }
410
411 ESP_LOGD(TAG, LOG_FMT("httpd_req_new"));
412 if (httpd_req_new(hd, session) != ESP_OK) {
413 return ESP_FAIL;
414 }
415 ESP_LOGD(TAG, LOG_FMT("httpd_req_delete"));
416 if (httpd_req_delete(hd) != ESP_OK) {
417 return ESP_FAIL;
418 }
419 ESP_LOGD(TAG, LOG_FMT("success"));
420 session->lru_counter = ++hd->lru_counter;
421 return ESP_OK;
422 }
423
httpd_sess_update_lru_counter(httpd_handle_t handle,int sockfd)424 esp_err_t httpd_sess_update_lru_counter(httpd_handle_t handle, int sockfd)
425 {
426 if (handle == NULL) {
427 return ESP_ERR_INVALID_ARG;
428 }
429
430 struct httpd_data *hd = (struct httpd_data *) handle;
431
432 enum_context_t context = {
433 .task = HTTPD_TASK_FIND_FD,
434 .fd = sockfd
435 };
436 httpd_sess_enum(hd, enum_function, &context);
437 if (context.session) {
438 context.session->lru_counter = ++hd->lru_counter;
439 return ESP_OK;
440 }
441 return ESP_ERR_NOT_FOUND;
442 }
443
httpd_sess_close_lru(struct httpd_data * hd)444 esp_err_t httpd_sess_close_lru(struct httpd_data *hd)
445 {
446 enum_context_t context = {
447 .task = HTTPD_TASK_FIND_LOWEST_LRU,
448 .lru_counter = UINT64_MAX,
449 .fd = -1
450 };
451 httpd_sess_enum(hd, enum_function, &context);
452 if (!context.session) {
453 return ESP_OK;
454 }
455 ESP_LOGD(TAG, LOG_FMT("Closing session with fd %d"), context.session->fd);
456 context.session->lru_socket = true;
457 return httpd_sess_trigger_close_(hd, context.session);
458 }
459
httpd_sess_trigger_close_(httpd_handle_t handle,struct sock_db * session)460 esp_err_t httpd_sess_trigger_close_(httpd_handle_t handle, struct sock_db *session)
461 {
462 if (!session) {
463 return ESP_ERR_NOT_FOUND;
464 }
465 return httpd_queue_work(handle, httpd_sess_close, session);
466 }
467
httpd_sess_trigger_close(httpd_handle_t handle,int sockfd)468 esp_err_t httpd_sess_trigger_close(httpd_handle_t handle, int sockfd)
469 {
470 struct sock_db *session = httpd_sess_get(handle, sockfd);
471 if (!session) {
472 return ESP_ERR_NOT_FOUND;
473 }
474 return httpd_sess_trigger_close_(handle, session);
475 }
476
httpd_sess_close_all(struct httpd_data * hd)477 void httpd_sess_close_all(struct httpd_data *hd)
478 {
479 enum_context_t context = {
480 .task = HTTPD_TASK_CLOSE,
481 .hd = hd
482 };
483 httpd_sess_enum(hd, enum_function, &context);
484 }
485