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