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 #include <freertos/FreeRTOS.h>
16 #include <freertos/task.h>
17 #include <esp_log.h>
18 #include <esp_err.h>
19 
20 #include <esp_http_server.h>
21 
22 #include <protocomm.h>
23 #include <protocomm_httpd.h>
24 
25 #include "protocomm_priv.h"
26 
27 static const char *TAG = "protocomm_httpd";
28 static protocomm_t *pc_httpd; /* The global protocomm instance for HTTPD */
29 static bool pc_ext_httpd_handle_provided = false;
30 /* The socket session id, which is basically just the socket number */
31 static uint32_t sock_session_id = PROTOCOMM_NO_SESSION_ID;
32 /* Cookie session id, which is a random number passed through HTTP cookies */
33 static uint32_t cookie_session_id = PROTOCOMM_NO_SESSION_ID;
34 
35 #define MAX_REQ_BODY_LEN 4096
36 
protocomm_httpd_session_close(void * ctx)37 static void protocomm_httpd_session_close(void *ctx)
38 {
39     /* When a socket session closes, we just reset the sock_session_id value.
40      * Thereafter, only cookie_session_id would get used to check if the subsequent
41      * request is for the same session.
42      */
43     if (sock_session_id != PROTOCOMM_NO_SESSION_ID) {
44         ESP_LOGW(TAG, "Resetting socket session id as socket %d was closed", sock_session_id);
45         sock_session_id = PROTOCOMM_NO_SESSION_ID;
46     }
47 }
48 
common_post_handler(httpd_req_t * req)49 static esp_err_t common_post_handler(httpd_req_t *req)
50 {
51     esp_err_t ret;
52     uint8_t *outbuf = NULL;
53     char *req_body = NULL;
54     const char *ep_name = NULL;
55     ssize_t outlen;
56 
57     int cur_sock_session_id = httpd_req_to_sockfd(req);
58     int cur_cookie_session_id = 0;
59     char cookie_buf[20] = {0};
60     bool same_session = false;
61 
62     /* Check if any cookie is available in the received headers */
63     if (httpd_req_get_hdr_value_str(req, "Cookie", cookie_buf, sizeof(cookie_buf)) == ESP_OK) {
64         ESP_LOGD(TAG, "Received cookie %s", cookie_buf);
65         char session_cookie[20] = {0};
66         snprintf(session_cookie, sizeof(session_cookie), "session=%u", cookie_session_id);
67         /* If a cookie is found, check it against the session id. If it matches,
68          * it means that this is a continuation of the same session.
69          */
70         if (strcmp(session_cookie, cookie_buf) == 0) {
71             ESP_LOGD(TAG, "Continuing Session %u", cookie_session_id);
72             cur_cookie_session_id = cookie_session_id;
73             /* If we reach here, it means that the client supports cookies and so the
74              * socket session id would no more be required for checking.
75              */
76             sock_session_id = PROTOCOMM_NO_SESSION_ID;
77             same_session = true;
78         }
79     } else if (cur_sock_session_id == sock_session_id) {
80         /* If the socket number matches, we assume it to be the same session */
81         ESP_LOGD(TAG, "Continuing Socket Session %u", sock_session_id);
82         cur_cookie_session_id = cookie_session_id;
83         same_session = true;
84     }
85     if (!same_session) {
86         /* If the received request is not a continuation of an existing session,
87          * first close any existing sessions as applicable.
88          */
89         if (cookie_session_id != PROTOCOMM_NO_SESSION_ID) {
90             ESP_LOGW(TAG, "Closing session with ID: %u", cookie_session_id);
91             if (pc_httpd->sec && pc_httpd->sec->close_transport_session) {
92                 ret = pc_httpd->sec->close_transport_session(pc_httpd->sec_inst, cookie_session_id);
93                 if (ret != ESP_OK) {
94                     ESP_LOGW(TAG, "Error closing session with ID: %u", cookie_session_id);
95                 }
96             }
97             cookie_session_id = PROTOCOMM_NO_SESSION_ID;
98             sock_session_id = PROTOCOMM_NO_SESSION_ID;
99         }
100         /* Initialize new security session. A random number will be assigned to the session */
101         cur_cookie_session_id = esp_random();
102         ESP_LOGD(TAG, "Creating new session: %u", cur_cookie_session_id);
103         if (pc_httpd->sec && pc_httpd->sec->new_transport_session) {
104             ret = pc_httpd->sec->new_transport_session(pc_httpd->sec_inst, cur_cookie_session_id);
105             if (ret != ESP_OK) {
106                 ESP_LOGE(TAG, "Failed to launch new session with ID: %u", cur_cookie_session_id);
107                 ret = ESP_FAIL;
108                 goto out;
109             }
110             req->sess_ctx = pc_httpd->sec_inst;
111             req->free_ctx = protocomm_httpd_session_close;
112 
113         }
114         cookie_session_id = cur_cookie_session_id;
115         sock_session_id = cur_sock_session_id;
116         ESP_LOGD(TAG, "New socket session ID: %d", sock_session_id);
117     }
118 
119     if (req->content_len <= 0) {
120         ESP_LOGE(TAG, "Content length not found");
121         ret = ESP_FAIL;
122         goto out;
123     } else if (req->content_len > MAX_REQ_BODY_LEN) {
124         ESP_LOGE(TAG, "Request content length should be less than 4kb");
125         ret = ESP_FAIL;
126         goto out;
127     }
128 
129     req_body = (char *) malloc(req->content_len);
130     if (!req_body) {
131         ESP_LOGE(TAG, "Unable to allocate for request length %d", req->content_len);
132         ret = ESP_ERR_NO_MEM;
133         goto out;
134     }
135 
136     size_t recv_size = 0;
137     while (recv_size < req->content_len) {
138         ret = httpd_req_recv(req, req_body + recv_size, req->content_len - recv_size);
139         if (ret <= 0) {
140             ret = ESP_FAIL;
141             goto out;
142         }
143         recv_size += ret;
144     }
145 
146     /* Extract the endpoint name from URI string of type "/ep_name" */
147     ep_name = req->uri + 1;
148 
149     ret = protocomm_req_handle(pc_httpd, ep_name, cookie_session_id,
150                                (uint8_t *)req_body, recv_size, &outbuf, &outlen);
151 
152     if (ret != ESP_OK) {
153         ESP_LOGE(TAG, "Data handler failed");
154         ret = ESP_FAIL;
155         goto out;
156     }
157     /* If this is a new session, send the session id in a cookie */
158     if (!same_session) {
159         snprintf(cookie_buf, sizeof(cookie_buf), "session=%u", cookie_session_id);
160         ESP_LOGD(TAG, "Setting cookie %s", cookie_buf);
161         httpd_resp_set_hdr(req, "Set-Cookie", cookie_buf);
162     }
163 
164     ret = httpd_resp_send(req, (char *)outbuf, outlen);
165     if (ret != ESP_OK) {
166         ESP_LOGE(TAG, "HTTP send failed");
167         ret = ESP_FAIL;
168         goto out;
169     }
170     ret = ESP_OK;
171 out:
172     if (req_body) {
173         free(req_body);
174     }
175     if (outbuf) {
176         free(outbuf);
177     }
178     return ret;
179 }
180 
protocomm_httpd_add_endpoint(const char * ep_name,protocomm_req_handler_t req_handler,void * priv_data)181 static esp_err_t protocomm_httpd_add_endpoint(const char *ep_name,
182                                        protocomm_req_handler_t req_handler,
183                                        void *priv_data)
184 {
185     if (pc_httpd == NULL) {
186         return ESP_ERR_INVALID_STATE;
187     }
188 
189     ESP_LOGD(TAG, "Adding endpoint : %s", ep_name);
190 
191     /* Construct URI name by prepending '/' to ep_name */
192     char* ep_uri = calloc(1, strlen(ep_name) + 2);
193     if (!ep_uri) {
194         ESP_LOGE(TAG, "Malloc failed for ep uri");
195         return ESP_ERR_NO_MEM;
196     }
197 
198     /* Create URI handler structure */
199     sprintf(ep_uri, "/%s", ep_name);
200     httpd_uri_t config_handler = {
201         .uri      = ep_uri,
202         .method   = HTTP_POST,
203         .handler  = common_post_handler,
204         .user_ctx = NULL
205     };
206 
207     /* Register URI handler */
208     esp_err_t err;
209     httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
210     if ((err = httpd_register_uri_handler(*server, &config_handler)) != ESP_OK) {
211         ESP_LOGE(TAG, "Uri handler register failed: %s", esp_err_to_name(err));
212         free(ep_uri);
213         return ESP_FAIL;
214     }
215 
216     free(ep_uri);
217     return ESP_OK;
218 }
219 
protocomm_httpd_remove_endpoint(const char * ep_name)220 static esp_err_t protocomm_httpd_remove_endpoint(const char *ep_name)
221 {
222     if (pc_httpd == NULL) {
223         return ESP_ERR_INVALID_STATE;
224     }
225 
226     ESP_LOGD(TAG, "Removing endpoint : %s", ep_name);
227 
228     /* Construct URI name by prepending '/' to ep_name */
229     char* ep_uri = calloc(1, strlen(ep_name) + 2);
230     if (!ep_uri) {
231         ESP_LOGE(TAG, "Malloc failed for ep uri");
232         return ESP_ERR_NO_MEM;
233     }
234     sprintf(ep_uri, "/%s", ep_name);
235 
236     /* Unregister URI handler */
237     esp_err_t err;
238     httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
239     if ((err = httpd_unregister_uri_handler(*server, ep_uri, HTTP_POST)) != ESP_OK) {
240         ESP_LOGE(TAG, "Uri handler de-register failed: %s", esp_err_to_name(err));
241         free(ep_uri);
242         return ESP_FAIL;
243     }
244 
245     free(ep_uri);
246     return ESP_OK;
247 }
248 
protocomm_httpd_start(protocomm_t * pc,const protocomm_httpd_config_t * config)249 esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config)
250 {
251     if (!pc || !config) {
252         return ESP_ERR_INVALID_ARG;
253     }
254 
255     if (pc_httpd) {
256         if (pc == pc_httpd) {
257             ESP_LOGE(TAG, "HTTP server already running for this protocomm instance");
258             return ESP_ERR_INVALID_STATE;
259         }
260         ESP_LOGE(TAG, "HTTP server started for another protocomm instance");
261         return ESP_ERR_NOT_SUPPORTED;
262     }
263 
264     if (config->ext_handle_provided) {
265         if (config->data.handle) {
266             pc->priv = config->data.handle;
267             pc_ext_httpd_handle_provided = true;
268         } else {
269             return ESP_ERR_INVALID_ARG;
270         }
271     } else {
272         /* Private data will point to the HTTP server handle */
273         pc->priv = calloc(1, sizeof(httpd_handle_t));
274         if (!pc->priv) {
275             ESP_LOGE(TAG, "Malloc failed for HTTP server handle");
276             return ESP_ERR_NO_MEM;
277         }
278 
279         /* Configure the HTTP server */
280         httpd_config_t server_config   = HTTPD_DEFAULT_CONFIG();
281         server_config.server_port      = config->data.config.port;
282         server_config.stack_size       = config->data.config.stack_size;
283         server_config.task_priority    = config->data.config.task_priority;
284         server_config.lru_purge_enable = true;
285         server_config.max_open_sockets = 1;
286 
287         esp_err_t err;
288         if ((err = httpd_start((httpd_handle_t *)pc->priv, &server_config)) != ESP_OK) {
289             ESP_LOGE(TAG, "Failed to start http server: %s", esp_err_to_name(err));
290             free(pc->priv);
291             return err;
292         }
293     }
294     pc->add_endpoint    = protocomm_httpd_add_endpoint;
295     pc->remove_endpoint = protocomm_httpd_remove_endpoint;
296     pc_httpd = pc;
297     cookie_session_id = PROTOCOMM_NO_SESSION_ID;
298     sock_session_id = PROTOCOMM_NO_SESSION_ID;
299     return ESP_OK;
300 }
301 
protocomm_httpd_stop(protocomm_t * pc)302 esp_err_t protocomm_httpd_stop(protocomm_t *pc)
303 {
304     if ((pc != NULL) && (pc == pc_httpd)) {
305         if (!pc_ext_httpd_handle_provided) {
306             httpd_handle_t *server_handle = (httpd_handle_t *) pc_httpd->priv;
307             httpd_stop(*server_handle);
308             free(server_handle);
309         } else {
310             pc_ext_httpd_handle_provided = false;
311         }
312         pc_httpd->priv = NULL;
313         pc_httpd = NULL;
314         cookie_session_id = PROTOCOMM_NO_SESSION_ID;
315         sock_session_id = PROTOCOMM_NO_SESSION_ID;
316         return ESP_OK;
317     }
318     return ESP_ERR_INVALID_ARG;
319 }
320