diff --git a/docs/c-api/mbuf.h/intro.md b/docs/c-api/mbuf.h/intro.md index 056369a2..600a1e09 100644 --- a/docs/c-api/mbuf.h/intro.md +++ b/docs/c-api/mbuf.h/intro.md @@ -4,9 +4,12 @@ symbol_kind: "intro" decl_name: "mbuf.h" items: - { name: mbuf_append.md } + - { name: mbuf_append_and_free.md } + - { name: mbuf_clear.md } - { name: mbuf_free.md } - { name: mbuf_init.md } - { name: mbuf_insert.md } + - { name: mbuf_move.md } - { name: mbuf_remove.md } - { name: mbuf_resize.md } - { name: mbuf_trim.md } diff --git a/docs/c-api/mbuf.h/mbuf_append_and_free.md b/docs/c-api/mbuf.h/mbuf_append_and_free.md new file mode 100644 index 00000000..e7be33ac --- /dev/null +++ b/docs/c-api/mbuf.h/mbuf_append_and_free.md @@ -0,0 +1,13 @@ +--- +title: "mbuf_append_and_free()" +decl_name: "mbuf_append_and_free" +symbol_kind: "func" +signature: | + size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size); +--- + +Appends data to the Mbuf and frees it (data must be heap-allocated). + +Returns the number of bytes appended or 0 if out of memory. +data is freed irrespective of return value. + diff --git a/docs/c-api/mbuf.h/mbuf_clear.md b/docs/c-api/mbuf.h/mbuf_clear.md new file mode 100644 index 00000000..afefc21a --- /dev/null +++ b/docs/c-api/mbuf.h/mbuf_clear.md @@ -0,0 +1,10 @@ +--- +title: "mbuf_clear()" +decl_name: "mbuf_clear" +symbol_kind: "func" +signature: | + void mbuf_clear(struct mbuf *); +--- + +Removes all the data from mbuf (if any). + diff --git a/docs/c-api/mbuf.h/mbuf_move.md b/docs/c-api/mbuf.h/mbuf_move.md new file mode 100644 index 00000000..61e20853 --- /dev/null +++ b/docs/c-api/mbuf.h/mbuf_move.md @@ -0,0 +1,10 @@ +--- +title: "mbuf_move()" +decl_name: "mbuf_move" +symbol_kind: "func" +signature: | + void mbuf_move(struct mbuf *from, struct mbuf *to); +--- + +Moves the state from one mbuf to the other. + diff --git a/docs/c-api/mg_http.h/mg_set_protocol_http_websocket.md b/docs/c-api/mg_http.h/mg_set_protocol_http_websocket.md index a78ff358..7320ae76 100644 --- a/docs/c-api/mg_http.h/mg_set_protocol_http_websocket.md +++ b/docs/c-api/mg_http.h/mg_set_protocol_http_websocket.md @@ -30,7 +30,9 @@ The user-defined event handler will receive following extra events: - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received the WebSocket handshake request. `ev_data` contains parsed HTTP request. - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed the WebSocket - handshake. `ev_data` is `NULL`. + handshake. `ev_data` is a `struct http_message` containing the + client's request (server mode) or server's response (client). + In client mode handler can examine `resp_code`, which should be 101. - MG_EV_WEBSOCKET_FRAME: new WebSocket frame has arrived. `ev_data` is `struct websocket_message *` diff --git a/docs/c-api/mg_http.h/struct_mg_http_multipart_part.md b/docs/c-api/mg_http.h/struct_mg_http_multipart_part.md index cd2d0c2e..ec65f9d9 100644 --- a/docs/c-api/mg_http.h/struct_mg_http_multipart_part.md +++ b/docs/c-api/mg_http.h/struct_mg_http_multipart_part.md @@ -9,6 +9,14 @@ signature: | struct mg_str data; int status; /* <0 on error */ void *user_data; + /* + * User handler can indicate how much of the data was consumed + * by setting this variable. By default, it is assumed that all + * data has been consumed by the handler. + * If not all data was consumed, user's handler will be invoked again later + * with the remainder. + */ + size_t num_data_consumed; }; --- diff --git a/docs/c-api/mg_net.h/mg_broadcast.md b/docs/c-api/mg_net.h/mg_broadcast.md index cef0b379..6e966afe 100644 --- a/docs/c-api/mg_net.h/mg_broadcast.md +++ b/docs/c-api/mg_net.h/mg_broadcast.md @@ -16,5 +16,5 @@ that can be, and must be, called from a different (non-IO) thread. `func` callback function will be called by the IO thread for each connection. When called, the event will be `MG_EV_POLL`, and a message will be passed as the `ev_data` pointer. Maximum message size is capped -by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes. +by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes by default. diff --git a/docs/c-api/mg_net.h/struct_mg_connection.md b/docs/c-api/mg_net.h/struct_mg_connection.md index be69d16f..cf6bd7ff 100644 --- a/docs/c-api/mg_net.h/struct_mg_connection.md +++ b/docs/c-api/mg_net.h/struct_mg_connection.md @@ -46,6 +46,7 @@ signature: | #define MG_F_WANT_READ (1 << 6) /* SSL specific */ #define MG_F_WANT_WRITE (1 << 7) /* SSL specific */ #define MG_F_IS_WEBSOCKET (1 << 8) /* Websocket specific */ + #define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */ /* Flags that are settable by user */ #define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */ diff --git a/examples/CC3200/bm222.c b/examples/CC3200/bm222.c index 416c410f..6fb2517a 100644 --- a/examples/CC3200/bm222.c +++ b/examples/CC3200/bm222.c @@ -98,7 +98,8 @@ bool bm222_get_data(struct bm222_ctx *ctx) { s->y = v[3]; s->z = v[5]; if (d > max_d) max_d = d; - LOG(LL_VERBOSE_DEBUG, ("dx %d dy %d dz %d d %d", dx, dy, dz, d)); + LOG(LL_VERBOSE_DEBUG, + ("dx %d dy %d dz %d d %d", (int) dx, (int) dy, (int) dz, (int) d)); } } return (overflow ? bm222_fifo_init(ctx) : true); /* Clear the ovf flag. */ diff --git a/examples/CC3200/cs_dbg.h b/examples/CC3200/cs_dbg.h index e987c427..8422ca3b 100644 --- a/examples/CC3200/cs_dbg.h +++ b/examples/CC3200/cs_dbg.h @@ -1,11 +1,25 @@ /* - * Copyright (c) 2014-2016 Cesanta Software Limited + * Copyright (c) 2014-2018 Cesanta Software Limited * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #ifndef CS_COMMON_CS_DBG_H_ #define CS_COMMON_CS_DBG_H_ +#include "common/platform.h" + #if CS_ENABLE_STDIO #include #endif @@ -14,6 +28,10 @@ #define CS_ENABLE_DEBUG 0 #endif +#ifndef CS_LOG_PREFIX_LEN +#define CS_LOG_PREFIX_LEN 24 +#endif + #ifndef CS_LOG_ENABLE_TS_DIFF #define CS_LOG_ENABLE_TS_DIFF 0 #endif @@ -22,6 +40,9 @@ extern "C" { #endif /* __cplusplus */ +/* + * Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it. + */ enum cs_log_level { LL_NONE = -1, LL_ERROR = 0, @@ -34,29 +55,78 @@ enum cs_log_level { _LL_MAX = 5, }; +/* + * Set max log level to print; messages with the level above the given one will + * not be printed. + */ void cs_log_set_level(enum cs_log_level level); +/* + * A comma-separated set of prefix=level. + * prefix is matched against the log prefix exactly as printed, including line + * number, but partial match is ok. Check stops on first matching entry. + * If nothing matches, default level is used. + * + * Examples: + * main.c:=4 - everything from main C at verbose debug level. + * mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_* + * + */ +void cs_log_set_file_level(const char *file_level); + +/* + * Helper function which prints message prefix with the given `level`. + * If message should be printed (according to the current log level + * and filter), prints the prefix and returns 1, otherwise returns 0. + * + * Clients should typically just use `LOG()` macro. + */ +int cs_log_print_prefix(enum cs_log_level level, const char *fname, int line); + +extern enum cs_log_level cs_log_level; + #if CS_ENABLE_STDIO +/* + * Set file to write logs into. If `NULL`, logs go to `stderr`. + */ void cs_log_set_file(FILE *file); -extern enum cs_log_level cs_log_threshold; -void cs_log_print_prefix(const char *func); -void cs_log_printf(const char *fmt, ...); +/* + * Prints log to the current log file, appends "\n" in the end and flushes the + * stream. + */ +void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); -#define LOG(l, x) \ - if (cs_log_threshold >= l) { \ - cs_log_print_prefix(__func__); \ - cs_log_printf x; \ - } +#if CS_ENABLE_STDIO + +/* + * Format and print message `x` with the given level `l`. Example: + * + * ```c + * LOG(LL_INFO, ("my info message: %d", 123)); + * LOG(LL_DEBUG, ("my debug message: %d", 123)); + * ``` + */ +#define LOG(l, x) \ + do { \ + if (cs_log_print_prefix(l, __FILE__, __LINE__)) { \ + cs_log_printf x; \ + } \ + } while (0) + +#else + +#define LOG(l, x) ((void) l) + +#endif #ifndef CS_NDEBUG -#define DBG(x) \ - if (cs_log_threshold >= LL_VERBOSE_DEBUG) { \ - cs_log_print_prefix(__func__); \ - cs_log_printf x; \ - } +/* + * Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))` + */ +#define DBG(x) LOG(LL_VERBOSE_DEBUG, x) #else /* NDEBUG */ diff --git a/examples/CC3200/main.c b/examples/CC3200/main.c index 700419aa..c37c0e1f 100644 --- a/examples/CC3200/main.c +++ b/examples/CC3200/main.c @@ -50,7 +50,7 @@ #define BM222_ADDR 0x18 #define TMP006_ADDR 0x41 -void fs_slfs_set_new_file_size(const char *name, size_t size); +void fs_slfs_set_file_size(const char *name, size_t size); static const char *upload_form = "\ @@ -70,7 +70,7 @@ static struct mg_str upload_fname(struct mg_connection *nc, if (nc->user_data != NULL) { intptr_t cl = (intptr_t) nc->user_data; if (cl >= 0) { - fs_slfs_set_new_file_size(fn + 3, cl); + fs_slfs_set_file_size(fn + 3, cl); } } lfn.len = fname.len + 4; diff --git a/examples/CC3200/wifi.c b/examples/CC3200/wifi.c index 035772f6..9f5b9644 100644 --- a/examples/CC3200/wifi.c +++ b/examples/CC3200/wifi.c @@ -25,7 +25,7 @@ void SimpleLinkWlanEventHandler(SlWlanEvent_t *e) { LOG(LL_INFO, ("WiFi: station disconnected")); break; default: - LOG(LL_INFO, ("WiFi: event %d", e->Event)); + LOG(LL_INFO, ("WiFi: event %d", (int) e->Event)); } } @@ -39,7 +39,7 @@ void SimpleLinkNetAppEventHandler(SlNetAppEvent_t *e) { } else if (e->Event == SL_NETAPP_IP_LEASED_EVENT) { LOG(LL_INFO, ("IP leased")); } else { - LOG(LL_INFO, ("NetApp event %d", e->Event)); + LOG(LL_INFO, ("NetApp event %d", (int) e->Event)); } } diff --git a/examples/websocket_chat_client/websocket_chat_client.c b/examples/websocket_chat_client/websocket_chat_client.c index 8191b4bf..c74ef4bb 100644 --- a/examples/websocket_chat_client/websocket_chat_client.c +++ b/examples/websocket_chat_client/websocket_chat_client.c @@ -14,7 +14,6 @@ static int s_done = 0; static int s_is_connected = 0; static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { - struct websocket_message *wm = (struct websocket_message *) ev_data; (void) nc; switch (ev) { @@ -26,8 +25,14 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { break; } case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { - printf("-- Connected\n"); - s_is_connected = 1; + struct http_message *hm = (struct http_message *) ev_data; + if (hm->resp_code == 101) { + printf("-- Connected\n"); + s_is_connected = 1; + } else { + printf("-- Connection failed! HTTP code %d\n", hm->resp_code); + /* Connection will be closed after this. */ + } break; } case MG_EV_POLL: { @@ -66,6 +71,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { break; } case MG_EV_WEBSOCKET_FRAME: { + struct websocket_message *wm = (struct websocket_message *) ev_data; printf("%.*s\n", (int) wm->size, wm->data); break; } diff --git a/mongoose.c b/mongoose.c index 3174523a..9026762e 100644 --- a/mongoose.c +++ b/mongoose.c @@ -38,7 +38,9 @@ /* Amalgamated: #include "mg_http.h" */ /* Amalgamated: #include "mg_net.h" */ +#ifndef MG_CTL_MSG_MESSAGE_SIZE #define MG_CTL_MSG_MESSAGE_SIZE 8192 +#endif /* internals that need to be accessible in unit tests */ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc, @@ -460,6 +462,10 @@ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len) { #define CS_ENABLE_DEBUG 0 #endif +#ifndef CS_LOG_PREFIX_LEN +#define CS_LOG_PREFIX_LEN 24 +#endif + #ifndef CS_LOG_ENABLE_TS_DIFF #define CS_LOG_ENABLE_TS_DIFF 0 #endif @@ -490,51 +496,28 @@ enum cs_log_level { void cs_log_set_level(enum cs_log_level level); /* - * Set log filter. NULL (a default) logs everything. - * Otherwise, function name and file name will be tested against the given - * pattern, and only matching messages will be printed. + * A comma-separated set of prefix=level. + * prefix is matched against the log prefix exactly as printed, including line + * number, but partial match is ok. Check stops on first matching entry. + * If nothing matches, default level is used. * - * For the pattern syntax, refer to `mg_match_prefix()` in `str_util.h`. + * Examples: + * main.c:=4 - everything from main C at verbose debug level. + * mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_* * - * Example: - * ```c - * void foo(void) { - * LOG(LL_INFO, ("hello from foo")); - * } - * - * void bar(void) { - * LOG(LL_INFO, ("hello from bar")); - * } - * - * void test(void) { - * cs_log_set_filter(NULL); - * foo(); - * bar(); - * - * cs_log_set_filter("f*"); - * foo(); - * bar(); // Will NOT print anything - * - * cs_log_set_filter("bar"); - * foo(); // Will NOT print anything - * bar(); - * } - * ``` */ -void cs_log_set_filter(const char *pattern); +void cs_log_set_file_level(const char *file_level); /* - * Helper function which prints message prefix with the given `level`, function - * name `func` and `filename`. If message should be printed (accordingly to the - * current log level and filter), prints the prefix and returns 1, otherwise - * returns 0. + * Helper function which prints message prefix with the given `level`. + * If message should be printed (according to the current log level + * and filter), prints the prefix and returns 1, otherwise returns 0. * * Clients should typically just use `LOG()` macro. */ -int cs_log_print_prefix(enum cs_log_level level, const char *func, - const char *filename); +int cs_log_print_prefix(enum cs_log_level level, const char *fname, int line); -extern enum cs_log_level cs_log_threshold; +extern enum cs_log_level cs_log_level; #if CS_ENABLE_STDIO @@ -559,9 +542,11 @@ void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); * LOG(LL_DEBUG, ("my debug message: %d", 123)); * ``` */ -#define LOG(l, x) \ - do { \ - if (cs_log_print_prefix(l, __func__, __FILE__)) cs_log_printf x; \ +#define LOG(l, x) \ + do { \ + if (cs_log_print_prefix(l, __FILE__, __LINE__)) { \ + cs_log_printf x; \ + } \ } while (0) #else @@ -624,7 +609,7 @@ void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); /* Amalgamated: #include "common/cs_time.h" */ /* Amalgamated: #include "common/str_util.h" */ -enum cs_log_level cs_log_threshold WEAK = +enum cs_log_level cs_log_level WEAK = #if CS_ENABLE_DEBUG LL_VERBOSE_DEBUG; #else @@ -632,10 +617,9 @@ enum cs_log_level cs_log_threshold WEAK = #endif #if CS_ENABLE_STDIO -static char *s_filter_pattern = NULL; -static size_t s_filter_pattern_len; +static char *s_file_level = NULL; -void cs_log_set_filter(const char *pattern) WEAK; +void cs_log_set_file_level(const char *file_level) WEAK; FILE *cs_log_file WEAK = NULL; @@ -645,34 +629,62 @@ double cs_log_ts WEAK; enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE; -void cs_log_set_filter(const char *pattern) { - free(s_filter_pattern); - if (pattern != NULL) { - s_filter_pattern = strdup(pattern); - s_filter_pattern_len = strlen(pattern); +void cs_log_set_file_level(const char *file_level) { + char *fl = s_file_level; + if (file_level != NULL) { + s_file_level = strdup(file_level); } else { - s_filter_pattern = NULL; - s_filter_pattern_len = 0; + s_file_level = NULL; } + free(fl); } -int cs_log_print_prefix(enum cs_log_level, const char *, const char *) WEAK; -int cs_log_print_prefix(enum cs_log_level level, const char *func, - const char *filename) { - char prefix[21]; +int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) { + char prefix[CS_LOG_PREFIX_LEN], *q; + const char *p; + size_t fl = 0, ll = 0, pl = 0; - if (level > cs_log_threshold) return 0; - if (s_filter_pattern != NULL && - mg_match_prefix(s_filter_pattern, s_filter_pattern_len, func) == 0 && - mg_match_prefix(s_filter_pattern, s_filter_pattern_len, filename) == 0) { - return 0; + if (level > cs_log_level && s_file_level == NULL) return 0; + + p = file + strlen(file); + + while (p != file) { + const char c = *(p - 1); + if (c == '/' || c == '\\') break; + p--; + fl++; + } + + ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5); + if (fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2); + + pl = fl + 1 + ll; + memcpy(prefix, p, fl); + q = prefix + pl; + memset(q, ' ', sizeof(prefix) - pl); + do { + *(--q) = '0' + (ln % 10); + ln /= 10; + } while (ln > 0); + *(--q) = ':'; + + if (s_file_level != NULL) { + enum cs_log_level pll = cs_log_level; + struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl); + struct mg_str k, v; + while ((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) { + bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0); + if (!yes) continue; + pll = (enum cs_log_level)(*v.p - '0'); + break; + } + if (level > pll) return 0; } - strncpy(prefix, func, 20); - prefix[20] = '\0'; if (cs_log_file == NULL) cs_log_file = stderr; cs_log_cur_msg_level = level; - fprintf(cs_log_file, "%-20s ", prefix); + fwrite(prefix, 1, sizeof(prefix), cs_log_file); #if CS_LOG_ENABLE_TS_DIFF { double now = cs_time(); @@ -701,15 +713,15 @@ void cs_log_set_file(FILE *file) { #else -void cs_log_set_filter(const char *pattern) { - (void) pattern; +void cs_log_set_file_level(const char *file_level) { + (void) file_level; } #endif /* CS_ENABLE_STDIO */ void cs_log_set_level(enum cs_log_level level) WEAK; void cs_log_set_level(enum cs_log_level level) { - cs_log_threshold = level; + cs_log_level = level; #if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO cs_log_ts = cs_time(); #endif @@ -1612,6 +1624,22 @@ size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) { return mbuf_insert(a, a->len, buf, len); } +size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK; +size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) { + size_t ret; + /* Optimization: if the buffer is currently empty, + * take over the user-provided buffer. */ + if (a->len == 0) { + if (a->buf != NULL) free(a->buf); + a->buf = (char *) data; + a->len = a->size = len; + return len; + } + ret = mbuf_insert(a, a->len, data, len); + free(data); + return ret; +} + void mbuf_remove(struct mbuf *mb, size_t n) WEAK; void mbuf_remove(struct mbuf *mb, size_t n) { if (n > 0 && n <= mb->len) { @@ -1620,6 +1648,17 @@ void mbuf_remove(struct mbuf *mb, size_t n) { } } +void mbuf_clear(struct mbuf *mb) WEAK; +void mbuf_clear(struct mbuf *mb) { + mb->len = 0; +} + +void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK; +void mbuf_move(struct mbuf *from, struct mbuf *to) { + memcpy(to, from, sizeof(*to)); + memset(from, 0, sizeof(*from)); +} + #endif /* EXCLUDE_COMMON */ #ifdef MG_MODULE_LINES #line 1 "common/mg_str.c" @@ -1645,6 +1684,7 @@ void mbuf_remove(struct mbuf *mb, size_t n) { /* Amalgamated: #include "common/mg_str.h" */ /* Amalgamated: #include "common/platform.h" */ +#include #include #include @@ -1744,6 +1784,14 @@ int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) { return mg_strcmp(s1, s2); } +void mg_strfree(struct mg_str *s) WEAK; +void mg_strfree(struct mg_str *s) { + char *sp = (char *) s->p; + s->p = NULL; + s->len = 0; + if (sp != NULL) free(sp); +} + const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle) WEAK; const char *mg_strstr(const struct mg_str haystack, @@ -1769,6 +1817,13 @@ struct mg_str mg_strstrip(struct mg_str s) { } return s; } + +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK; +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) { + const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len); + if (s.len < prefix.len) return 0; + return (mg_strcmp(sp, prefix) == 0); +} #ifdef MG_MODULE_LINES #line 1 "common/str_util.c" #endif @@ -2437,8 +2492,16 @@ MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) { static int mg_do_recv(struct mg_connection *nc); int mg_if_poll(struct mg_connection *nc, double now) { - if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) || - (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) { + if (nc->flags & MG_F_CLOSE_IMMEDIATELY) { + mg_close_conn(nc); + return 0; + } else if (nc->flags & MG_F_SEND_AND_CLOSE) { + if (nc->send_mbuf.len == 0) { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + mg_close_conn(nc); + return 0; + } + } else if (nc->flags & MG_F_RECV_AND_CLOSE) { mg_close_conn(nc); return 0; } @@ -2481,6 +2544,13 @@ void mg_destroy_conn(struct mg_connection *conn, int destroy_if) { } void mg_close_conn(struct mg_connection *conn) { + /* See if there's any remaining data to deliver. Skip if user completely + * throttled the connection there will be no progress anyway. */ + if (conn->sock != INVALID_SOCKET && mg_do_recv(conn) == -2) { + /* Receive is throttled, wait. */ + conn->flags |= MG_F_RECV_AND_CLOSE; + return; + } #if MG_ENABLE_SSL if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) { mg_ssl_if_conn_close_notify(conn); @@ -2571,6 +2641,7 @@ void mg_mgr_free(struct mg_mgr *m) { for (conn = m->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; + conn->flags |= MG_F_CLOSE_IMMEDIATELY; mg_close_conn(conn); } @@ -2874,18 +2945,23 @@ static int mg_do_recv(struct mg_connection *nc) { ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) { return -1; } - len = recv_avail_size(nc, len); - if (len == 0) return -2; - if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) { - mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len); - } - buf = nc->recv_mbuf.buf + nc->recv_mbuf.len; - len = nc->recv_mbuf.size - nc->recv_mbuf.len; - if (nc->flags & MG_F_UDP) { - res = mg_recv_udp(nc, buf, len); - } else { - res = mg_recv_tcp(nc, buf, len); - } + do { + len = recv_avail_size(nc, len); + if (len == 0) { + res = -2; + break; + } + if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) { + mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len); + } + buf = nc->recv_mbuf.buf + nc->recv_mbuf.len; + len = nc->recv_mbuf.size - nc->recv_mbuf.len; + if (nc->flags & MG_F_UDP) { + res = mg_recv_udp(nc, buf, len); + } else { + res = mg_recv_tcp(nc, buf, len); + } + } while (res > 0 && !(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_UDP))); return res; } @@ -3002,7 +3078,9 @@ static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) { mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV); } #endif - mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + if (n != 0) { + mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + } } out: @@ -5884,6 +5962,7 @@ struct mg_http_multipart_stream { void *user_data; enum mg_http_multipart_stream_state state; int processing_part; + int data_avail; }; struct mg_reverse_proxy_data { @@ -6502,9 +6581,9 @@ void mg_http_handler(struct mg_connection *nc, int ev, if (io->len > 0 && (req_len = mg_parse_http(io->buf, io->len, hm, is_req)) > 0) { /* - * For HTTP messages without Content-Length, always send HTTP message - * before MG_EV_CLOSE message. - */ + * For HTTP messages without Content-Length, always send HTTP message + * before MG_EV_CLOSE message. + */ int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; hm->message.len = io->len; hm->body.len = io->buf + io->len - hm->body.p; @@ -6512,6 +6591,9 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_http_call_endpoint_handler(nc, ev2, hm); } pd->rcvd = 0; + if (pd->endpoint_handler != NULL && pd->endpoint_handler != nc->handler) { + mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL); + } } #if MG_ENABLE_FILESYSTEM @@ -6522,17 +6604,24 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_call(nc, nc->handler, nc->user_data, ev, ev_data); +#if MG_ENABLE_HTTP_STREAMING_MULTIPART + if (pd->mp_stream.boundary != NULL && + (ev == MG_EV_RECV || ev == MG_EV_POLL)) { + if (ev == MG_EV_RECV) { + pd->rcvd += *(int *) ev_data; + mg_http_multipart_continue(nc); + } else if (pd->mp_stream.data_avail) { + /* Try re-delivering the data. */ + mg_http_multipart_continue(nc); + } + return; + } +#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ + if (ev == MG_EV_RECV) { struct mg_str *s; pd->rcvd += *(int *) ev_data; -#if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (pd->mp_stream.boundary != NULL) { - mg_http_multipart_continue(nc); - return; - } -#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ - again: req_len = mg_parse_http(io->buf, io->len, hm, is_req); @@ -6560,16 +6649,23 @@ void mg_http_handler(struct mg_connection *nc, int ev, /* Do nothing, request is not yet fully buffered */ } #if MG_ENABLE_HTTP_WEBSOCKET - else if (nc->listener == NULL && - mg_get_http_header(hm, "Sec-WebSocket-Accept")) { + else if (nc->listener == NULL && (nc->flags & MG_F_IS_WEBSOCKET)) { /* We're websocket client, got handshake response from server. */ - /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ - mbuf_remove(io, req_len); - nc->proto_handler = mg_ws_handler; - nc->flags |= MG_F_IS_WEBSOCKET; - mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, - NULL); - mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); + DBG(("%p WebSocket upgrade code %d", nc, hm->resp_code)); + if (hm->resp_code == 101 && + mg_get_http_header(hm, "Sec-WebSocket-Accept")) { + /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ + mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, + hm); + mbuf_remove(io, req_len); + nc->proto_handler = mg_ws_handler; + mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); + } else { + mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, + hm); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + mbuf_remove(io, req_len); + } } else if (nc->listener != NULL && (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) { struct mg_http_endpoint *ep; @@ -6600,7 +6696,7 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_ws_handshake(nc, vec, hm); } mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, - NULL); + hm); mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); } } @@ -6727,8 +6823,9 @@ exit_mp: #define CONTENT_DISPOSITION "Content-Disposition: " -static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, - const char *data, size_t data_len) { +static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev, + const char *data, + size_t data_len) { struct mg_http_multipart_part mp; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); memset(&mp, 0, sizeof(mp)); @@ -6738,8 +6835,11 @@ static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, mp.user_data = pd->mp_stream.user_data; mp.data.p = data; mp.data.len = data_len; + mp.num_data_consumed = data_len; mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp); pd->mp_stream.user_data = mp.user_data; + pd->mp_stream.data_avail = (mp.num_data_consumed != data_len); + return mp.num_data_consumed; } static int mg_http_multipart_finalize(struct mg_connection *c) { @@ -6877,19 +6977,25 @@ static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) { boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary == NULL) { - int data_size = (io->len - (pd->mp_stream.boundary_len + 6)); - if (data_size > 0) { - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, - data_size); - mbuf_remove(io, data_size); + int data_len = (io->len - (pd->mp_stream.boundary_len + 6)); + if (data_len > 0) { + size_t consumed = mg_http_multipart_call_handler( + c, MG_EV_HTTP_PART_DATA, io->buf, (size_t) data_len); + mbuf_remove(io, consumed); } return 0; } else if (boundary != NULL) { - int data_size = (boundary - io->buf - 4); - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size); - mbuf_remove(io, (boundary - io->buf)); - pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; - return 1; + size_t data_len = ((size_t)(boundary - io->buf) - 4); + size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, + io->buf, data_len); + mbuf_remove(io, consumed); + if (consumed == data_len) { + mbuf_remove(io, 4); + pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; + return 1; + } else { + return 0; + } } else { return 0; } @@ -7667,7 +7773,7 @@ int mg_check_digest_auth(struct mg_str method, struct mg_str uri, struct mg_str nc, struct mg_str nonce, struct mg_str auth_domain, FILE *fp) { char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)]; - char expected_response[33]; + char exp_resp[33]; /* * Read passwords file line by line. If should have htdigest format, @@ -7681,11 +7787,10 @@ int mg_check_digest_auth(struct mg_str method, struct mg_str uri, /* Username and domain matched, check the password */ mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1), nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len, - qop.p, qop.len, expected_response); - LOG(LL_DEBUG, - ("%.*s %s %.*s %s", (int) username.len, username.p, f_domain, - (int) response.len, response.p, expected_response)); - return mg_ncasecmp(response.p, expected_response, response.len) == 0; + qop.p, qop.len, exp_resp); + LOG(LL_DEBUG, ("%.*s %s %.*s %s", (int) username.len, username.p, + f_domain, (int) response.len, response.p, exp_resp)); + return mg_ncasecmp(response.p, exp_resp, strlen(exp_resp)) == 0; } } @@ -8280,9 +8385,11 @@ void mg_http_send_digest_auth_request(struct mg_connection *c, domain, (unsigned long) mg_time()); } -static void mg_http_send_options(struct mg_connection *nc) { +static void mg_http_send_options(struct mg_connection *nc, + struct mg_serve_http_opts *opts) { + mg_send_response_line(nc, 200, opts->extra_headers); mg_printf(nc, "%s", - "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS" + "Allow: GET, POST, HEAD, CONNECT, OPTIONS" #if MG_ENABLE_HTTP_WEBDAV ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2" #endif @@ -8389,7 +8496,7 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, #endif #endif /* MG_ENABLE_HTTP_WEBDAV */ } else if (!mg_vcmp(&hm->method, "OPTIONS")) { - mg_http_send_options(nc); + mg_http_send_options(nc, opts); } else if (is_directory && index_file == NULL) { #if MG_ENABLE_DIRECTORY_LISTING if (strcmp(opts->enable_directory_listing, "yes") == 0) { @@ -10302,6 +10409,8 @@ void mg_send_websocket_handshake3v(struct mg_connection *nc, } mg_printf(nc, "\r\n"); + nc->flags |= MG_F_IS_WEBSOCKET; + mbuf_free(&auth); } @@ -10887,26 +10996,44 @@ static void mg_mqtt_proto_data_destructor(void *proto_data) { MG_FREE(proto_data); } +static struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) { + struct mg_str res = *topic; + const char *c = mg_strchr(*topic, '/'); + if (c != NULL) { + res.len = (c - topic->p); + topic->len -= (res.len + 1); + topic->p += (res.len + 1); + } else { + topic->len = 0; + } + return res; +} + +/* Refernce: https://mosquitto.org/man/mqtt-7.html */ int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) { - /* TODO(mkm): implement real matching */ - if (memchr(exp.p, '#', exp.len)) { - /* exp `foo/#` will become `foo/` */ - exp.len -= 1; - /* - * topic should be longer than the expression: e.g. topic `foo/bar` does - * match `foo/#`, but neither `foo` nor `foo/` do. - */ - if (topic.len <= exp.len) { + struct mg_str ec, tc; + if (exp.len == 0) return 0; + while (1) { + ec = mg_mqtt_next_topic_component(&exp); + tc = mg_mqtt_next_topic_component(&topic); + if (ec.len == 0) { + if (tc.len != 0) return 0; + if (exp.len == 0) break; + continue; + } + if (mg_vcmp(&ec, "+") == 0) { + if (tc.len == 0 && topic.len == 0) return 0; + continue; + } + if (mg_vcmp(&ec, "#") == 0) { + /* Must be the last component in the expression or it's invalid. */ + return (exp.len == 0); + } + if (mg_strcmp(ec, tc) != 0) { return 0; } - - /* Truncate topic so that it'll pass the next length check */ - topic.len = exp.len; } - if (topic.len != exp.len) { - return 0; - } - return strncmp(topic.p, exp.p, exp.len) == 0; + return (tc.len == 0 && topic.len == 0); } int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) { @@ -13405,7 +13532,9 @@ off_t fs_slfs_lseek(int fd, off_t offset, int whence); int fs_slfs_unlink(const char *filename); int fs_slfs_rename(const char *from, const char *to); -void fs_slfs_set_new_file_size(const char *name, size_t size); +void fs_slfs_set_file_size(const char *name, size_t size); +void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token); +void fs_slfs_unset_file_flags(const char *name); #endif /* defined(MG_FS_SLFS) */ @@ -13446,14 +13575,14 @@ void fs_slfs_set_new_file_size(const char *name, size_t size); /* Amalgamated: #include "common/mg_mem.h" */ #if SL_MAJOR_VERSION_NUM < 2 -int slfs_open(const unsigned char *fname, uint32_t flags) { +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) { _i32 fh; - _i32 r = sl_FsOpen(fname, flags, NULL /* token */, &fh); + _i32 r = sl_FsOpen(fname, flags, (unsigned long *) token, &fh); return (r < 0 ? r : fh); } #else /* SL_MAJOR_VERSION_NUM >= 2 */ -int slfs_open(const unsigned char *fname, uint32_t flags) { - return sl_FsOpen(fname, flags, NULL /* token */); +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) { + return sl_FsOpen(fname, flags, (unsigned long *) token); } #endif @@ -13469,9 +13598,11 @@ const char *drop_dir(const char *fname, bool *is_slfs); #define FS_SLFS_MAX_FILE_SIZE (64 * 1024) #endif -struct sl_file_size_hint { +struct sl_file_open_info { char *name; size_t size; + uint32_t flags; + uint32_t *token; }; struct sl_fd_info { @@ -13481,7 +13612,10 @@ struct sl_fd_info { }; static struct sl_fd_info s_sl_fds[MAX_OPEN_SLFS_FILES]; -static struct sl_file_size_hint s_sl_file_size_hints[MAX_OPEN_SLFS_FILES]; +static struct sl_file_open_info s_sl_file_open_infos[MAX_OPEN_SLFS_FILES]; + +static struct sl_file_open_info *fs_slfs_find_foi(const char *name, + bool create); static int sl_fs_to_errno(_i32 r) { DBG(("SL error: %d", (int) r)); @@ -13522,7 +13656,13 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) { _u32 am = 0; fi->size = (size_t) -1; int rw = (flags & 3); - size_t new_size = FS_SLFS_MAX_FILE_SIZE; + size_t new_size = 0; + struct sl_file_open_info *foi = + fs_slfs_find_foi(pathname, false /* create */); + if (foi != NULL) { + LOG(LL_DEBUG, ("FOI for %s: %d 0x%x %p", pathname, (int) foi->size, + (unsigned int) foi->flags, foi->token)); + } if (rw == O_RDONLY) { SlFsFileInfo_t sl_fi; _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi); @@ -13537,24 +13677,27 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) { return set_errno(ENOTSUP); } if (flags & O_CREAT) { - size_t i; - for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { - if (s_sl_file_size_hints[i].name != NULL && - strcmp(s_sl_file_size_hints[i].name, pathname) == 0) { - new_size = s_sl_file_size_hints[i].size; - MG_FREE(s_sl_file_size_hints[i].name); - s_sl_file_size_hints[i].name = NULL; - break; - } + if (foi->size > 0) { + new_size = foi->size; + } else { + new_size = FS_SLFS_MAX_FILE_SIZE; } am = FS_MODE_OPEN_CREATE(new_size, 0); } else { am = SL_FS_WRITE; } +#if SL_MAJOR_VERSION_NUM >= 2 + am |= SL_FS_OVERWRITE; +#endif } - fi->fh = slfs_open((_u8 *) pathname, am); - LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x) sz %u = %d", pathname, (int) am, - (unsigned int) new_size, (int) fi->fh)); + uint32_t *token = NULL; + if (foi != NULL) { + am |= foi->flags; + token = foi->token; + } + fi->fh = slfs_open((_u8 *) pathname, am, token); + LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x, %p) sz %u = %d", pathname, (int) am, + token, (unsigned int) new_size, (int) fi->fh)); int r; if (fi->fh >= 0) { fi->pos = 0; @@ -13658,16 +13801,46 @@ int fs_slfs_rename(const char *from, const char *to) { return set_errno(ENOTSUP); } -void fs_slfs_set_new_file_size(const char *name, size_t size) { - int i; +static struct sl_file_open_info *fs_slfs_find_foi(const char *name, + bool create) { + int i = 0; for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { - if (s_sl_file_size_hints[i].name == NULL) { - DBG(("File size hint: %s %d", name, (int) size)); - s_sl_file_size_hints[i].name = strdup(name); - s_sl_file_size_hints[i].size = size; + if (s_sl_file_open_infos[i].name != NULL && + strcmp(drop_dir(s_sl_file_open_infos[i].name, NULL), name) == 0) { break; } } + if (i != MAX_OPEN_SLFS_FILES) return &s_sl_file_open_infos[i]; + if (!create) return NULL; + for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { + if (s_sl_file_open_infos[i].name == NULL) break; + } + if (i == MAX_OPEN_SLFS_FILES) { + i = 0; /* Evict a random slot. */ + } + if (s_sl_file_open_infos[i].name != NULL) { + free(s_sl_file_open_infos[i].name); + } + s_sl_file_open_infos[i].name = strdup(name); + return &s_sl_file_open_infos[i]; +} + +void fs_slfs_set_file_size(const char *name, size_t size) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */); + foi->size = size; +} + +void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */); + foi->flags = flags; + foi->token = token; +} + +void fs_slfs_unset_file_flags(const char *name) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, false /* create */); + if (foi == NULL) return; + free(foi->name); + memset(foi, 0, sizeof(*foi)); } #endif /* defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS) */ @@ -14810,9 +14983,9 @@ bool pem_to_der(const char *pem_file, const char *der_file) { pf = fopen(pem_file, "r"); if (pf == NULL) goto clean; remove(der_file); - fs_slfs_set_new_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, - 2048); + fs_slfs_set_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, 2048); df = fopen(der_file, "w"); + fs_slfs_unset_file_flags(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN); if (df == NULL) goto clean; while (1) { char pem_buf[70]; @@ -15598,7 +15771,7 @@ struct tcp_recved_ctx { void tcp_recved_tcpip(void *arg) { struct tcp_recved_ctx *ctx = (struct tcp_recved_ctx *) arg; - tcp_recved(ctx->tpcb, ctx->len); + if (ctx->tpcb != NULL) tcp_recved(ctx->tpcb, ctx->len); } static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf, @@ -15805,7 +15978,6 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) { break; } case MG_SIG_CLOSE_CONN: { - nc->flags |= MG_F_SEND_AND_CLOSE; mg_close_conn(nc); break; } diff --git a/mongoose.h b/mongoose.h index 8fe13cd6..0f85b18b 100644 --- a/mongoose.h +++ b/mongoose.h @@ -23,7 +23,7 @@ #ifndef CS_MONGOOSE_SRC_COMMON_H_ #define CS_MONGOOSE_SRC_COMMON_H_ -#define MG_VERSION "6.13" +#define MG_VERSION "6.14" /* Local tweaks, applied before any of Mongoose's own headers. */ #ifdef MG_LOCALS @@ -1531,7 +1531,7 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc); #endif /* SL_MAJOR_VERSION_NUM < 2 */ -int slfs_open(const unsigned char *fname, uint32_t flags); +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token); #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */ @@ -2239,6 +2239,8 @@ struct mg_str mg_mk_str_n(const char *s, size_t len); /* Macro for initializing mg_str. */ #define MG_MK_STR(str_literal) \ { str_literal, sizeof(str_literal) - 1 } +#define MG_MK_STR_N(str_literal, len) \ + { str_literal, len } #define MG_NULL_STR \ { NULL, 0 } @@ -2278,6 +2280,11 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2); */ int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n); +/* + * Free the string (assuming it was heap allocated). + */ +void mg_strfree(struct mg_str *s); + /* * Finds the first occurrence of a substring `needle` in the `haystack`. */ @@ -2286,6 +2293,9 @@ const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); /* Strip whitespace at the start and the end of s */ struct mg_str mg_strstrip(struct mg_str s); +/* Returns 1 if s starts with the given prefix. */ +int mg_str_starts_with(struct mg_str s, struct mg_str prefix); + #ifdef __cplusplus } #endif @@ -2363,6 +2373,14 @@ void mbuf_free(struct mbuf *); */ size_t mbuf_append(struct mbuf *, const void *data, size_t data_size); +/* + * Appends data to the Mbuf and frees it (data must be heap-allocated). + * + * Returns the number of bytes appended or 0 if out of memory. + * data is freed irrespective of return value. + */ +size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size); + /* * Inserts data at a specified offset in the Mbuf. * @@ -2383,6 +2401,12 @@ void mbuf_remove(struct mbuf *, size_t data_size); */ void mbuf_resize(struct mbuf *, size_t new_size); +/* Moves the state from one mbuf to the other. */ +void mbuf_move(struct mbuf *from, struct mbuf *to); + +/* Removes all the data from mbuf (if any). */ +void mbuf_clear(struct mbuf *); + /* Shrinks an Mbuf by resizing its `size` to `len`. */ void mbuf_trim(struct mbuf *); @@ -2623,6 +2647,8 @@ const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, /* * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`. + * NB: Test return value's .p, not .len. On last itreation that yields result + * .len will be 0 but .p will not. When finished, .p will be NULL. */ struct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val, struct mg_str *eq_val); @@ -3930,6 +3956,7 @@ struct mg_connection { #define MG_F_WANT_READ (1 << 6) /* SSL specific */ #define MG_F_WANT_WRITE (1 << 7) /* SSL specific */ #define MG_F_IS_WEBSOCKET (1 << 8) /* Websocket specific */ +#define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */ /* Flags that are settable by user */ #define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */ @@ -4015,7 +4042,7 @@ int mg_mgr_poll(struct mg_mgr *mgr, int milli); * `func` callback function will be called by the IO thread for each * connection. When called, the event will be `MG_EV_POLL`, and a message will * be passed as the `ev_data` pointer. Maximum message size is capped - * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes. + * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes by default. */ void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data, size_t len); @@ -4753,6 +4780,14 @@ struct mg_http_multipart_part { struct mg_str data; int status; /* <0 on error */ void *user_data; + /* + * User handler can indicate how much of the data was consumed + * by setting this variable. By default, it is assumed that all + * data has been consumed by the handler. + * If not all data was consumed, user's handler will be invoked again later + * with the remainder. + */ + size_t num_data_consumed; }; /* SSI call context */ @@ -4771,7 +4806,7 @@ struct mg_ssi_call_ctx { #if MG_ENABLE_HTTP_WEBSOCKET #define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* struct http_message * */ -#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* NULL */ +#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* struct http_message * */ #define MG_EV_WEBSOCKET_FRAME 113 /* struct websocket_message * */ #define MG_EV_WEBSOCKET_CONTROL_FRAME 114 /* struct websocket_message * */ #endif @@ -4810,7 +4845,9 @@ struct mg_ssi_call_ctx { * - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received the WebSocket * handshake request. `ev_data` contains parsed HTTP request. * - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed the WebSocket - * handshake. `ev_data` is `NULL`. + * handshake. `ev_data` is a `struct http_message` containing the + * client's request (server mode) or server's response (client). + * In client mode handler can examine `resp_code`, which should be 101. * - MG_EV_WEBSOCKET_FRAME: new WebSocket frame has arrived. `ev_data` is * `struct websocket_message *` * diff --git a/src/common/cs_dbg.c b/src/common/cs_dbg.c index 594aa153..35c457e3 100644 --- a/src/common/cs_dbg.c +++ b/src/common/cs_dbg.c @@ -24,7 +24,7 @@ #include "common/cs_time.h" #include "common/str_util.h" -enum cs_log_level cs_log_threshold WEAK = +enum cs_log_level cs_log_level WEAK = #if CS_ENABLE_DEBUG LL_VERBOSE_DEBUG; #else @@ -32,10 +32,9 @@ enum cs_log_level cs_log_threshold WEAK = #endif #if CS_ENABLE_STDIO -static char *s_filter_pattern = NULL; -static size_t s_filter_pattern_len; +static char *s_file_level = NULL; -void cs_log_set_filter(const char *pattern) WEAK; +void cs_log_set_file_level(const char *file_level) WEAK; FILE *cs_log_file WEAK = NULL; @@ -45,34 +44,62 @@ double cs_log_ts WEAK; enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE; -void cs_log_set_filter(const char *pattern) { - free(s_filter_pattern); - if (pattern != NULL) { - s_filter_pattern = strdup(pattern); - s_filter_pattern_len = strlen(pattern); +void cs_log_set_file_level(const char *file_level) { + char *fl = s_file_level; + if (file_level != NULL) { + s_file_level = strdup(file_level); } else { - s_filter_pattern = NULL; - s_filter_pattern_len = 0; + s_file_level = NULL; } + free(fl); } -int cs_log_print_prefix(enum cs_log_level, const char *, const char *) WEAK; -int cs_log_print_prefix(enum cs_log_level level, const char *func, - const char *filename) { - char prefix[21]; +int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) { + char prefix[CS_LOG_PREFIX_LEN], *q; + const char *p; + size_t fl = 0, ll = 0, pl = 0; - if (level > cs_log_threshold) return 0; - if (s_filter_pattern != NULL && - mg_match_prefix(s_filter_pattern, s_filter_pattern_len, func) == 0 && - mg_match_prefix(s_filter_pattern, s_filter_pattern_len, filename) == 0) { - return 0; + if (level > cs_log_level && s_file_level == NULL) return 0; + + p = file + strlen(file); + + while (p != file) { + const char c = *(p - 1); + if (c == '/' || c == '\\') break; + p--; + fl++; + } + + ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5); + if (fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2); + + pl = fl + 1 + ll; + memcpy(prefix, p, fl); + q = prefix + pl; + memset(q, ' ', sizeof(prefix) - pl); + do { + *(--q) = '0' + (ln % 10); + ln /= 10; + } while (ln > 0); + *(--q) = ':'; + + if (s_file_level != NULL) { + enum cs_log_level pll = cs_log_level; + struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl); + struct mg_str k, v; + while ((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) { + bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0); + if (!yes) continue; + pll = (enum cs_log_level)(*v.p - '0'); + break; + } + if (level > pll) return 0; } - strncpy(prefix, func, 20); - prefix[20] = '\0'; if (cs_log_file == NULL) cs_log_file = stderr; cs_log_cur_msg_level = level; - fprintf(cs_log_file, "%-20s ", prefix); + fwrite(prefix, 1, sizeof(prefix), cs_log_file); #if CS_LOG_ENABLE_TS_DIFF { double now = cs_time(); @@ -101,15 +128,15 @@ void cs_log_set_file(FILE *file) { #else -void cs_log_set_filter(const char *pattern) { - (void) pattern; +void cs_log_set_file_level(const char *file_level) { + (void) file_level; } #endif /* CS_ENABLE_STDIO */ void cs_log_set_level(enum cs_log_level level) WEAK; void cs_log_set_level(enum cs_log_level level) { - cs_log_threshold = level; + cs_log_level = level; #if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO cs_log_ts = cs_time(); #endif diff --git a/src/common/cs_dbg.h b/src/common/cs_dbg.h index 5b9777c1..8422ca3b 100644 --- a/src/common/cs_dbg.h +++ b/src/common/cs_dbg.h @@ -28,6 +28,10 @@ #define CS_ENABLE_DEBUG 0 #endif +#ifndef CS_LOG_PREFIX_LEN +#define CS_LOG_PREFIX_LEN 24 +#endif + #ifndef CS_LOG_ENABLE_TS_DIFF #define CS_LOG_ENABLE_TS_DIFF 0 #endif @@ -58,51 +62,28 @@ enum cs_log_level { void cs_log_set_level(enum cs_log_level level); /* - * Set log filter. NULL (a default) logs everything. - * Otherwise, function name and file name will be tested against the given - * pattern, and only matching messages will be printed. + * A comma-separated set of prefix=level. + * prefix is matched against the log prefix exactly as printed, including line + * number, but partial match is ok. Check stops on first matching entry. + * If nothing matches, default level is used. * - * For the pattern syntax, refer to `mg_match_prefix()` in `str_util.h`. + * Examples: + * main.c:=4 - everything from main C at verbose debug level. + * mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_* * - * Example: - * ```c - * void foo(void) { - * LOG(LL_INFO, ("hello from foo")); - * } - * - * void bar(void) { - * LOG(LL_INFO, ("hello from bar")); - * } - * - * void test(void) { - * cs_log_set_filter(NULL); - * foo(); - * bar(); - * - * cs_log_set_filter("f*"); - * foo(); - * bar(); // Will NOT print anything - * - * cs_log_set_filter("bar"); - * foo(); // Will NOT print anything - * bar(); - * } - * ``` */ -void cs_log_set_filter(const char *pattern); +void cs_log_set_file_level(const char *file_level); /* - * Helper function which prints message prefix with the given `level`, function - * name `func` and `filename`. If message should be printed (accordingly to the - * current log level and filter), prints the prefix and returns 1, otherwise - * returns 0. + * Helper function which prints message prefix with the given `level`. + * If message should be printed (according to the current log level + * and filter), prints the prefix and returns 1, otherwise returns 0. * * Clients should typically just use `LOG()` macro. */ -int cs_log_print_prefix(enum cs_log_level level, const char *func, - const char *filename); +int cs_log_print_prefix(enum cs_log_level level, const char *fname, int line); -extern enum cs_log_level cs_log_threshold; +extern enum cs_log_level cs_log_level; #if CS_ENABLE_STDIO @@ -127,9 +108,11 @@ void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); * LOG(LL_DEBUG, ("my debug message: %d", 123)); * ``` */ -#define LOG(l, x) \ - do { \ - if (cs_log_print_prefix(l, __func__, __FILE__)) cs_log_printf x; \ +#define LOG(l, x) \ + do { \ + if (cs_log_print_prefix(l, __FILE__, __LINE__)) { \ + cs_log_printf x; \ + } \ } while (0) #else diff --git a/src/common/platforms/arm/arm_nsleep100_m3.S b/src/common/platforms/arm/arm_nsleep100_m3.S new file mode 100644 index 00000000..cae920e3 --- /dev/null +++ b/src/common/platforms/arm/arm_nsleep100_m3.S @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014-2019 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.arch armv7e-m +.syntax unified +.thumb + +/* These are required to satisfy TI linker. */ +.eabi_attribute Tag_ABI_align_needed, 1 +.eabi_attribute Tag_ABI_align_preserved, 1 + +.global mgos_nsleep100_impl +.global mgos_nsleep100_loop_count + +#ifdef STM32 +.section .RamFunc.mgos_nsleep100_impl +#else +.section .iram.mgos_nsleep100_impl +#endif +.type mgos_nsleep100_impl, %function +.align 4 + +mgos_nsleep100_impl: + ldr r3, =mgos_nsleep100_loop_count + ldr r3, [r3] + mul r0, r3 + mov r1, #6 + udiv r0, r0, r1 + cbz r0, xxx +lxx: + subs r0, #1 + bne lxx +xxx: + bx lr +.align 4 +.size mgos_nsleep100_impl, . - mgos_nsleep100_impl diff --git a/src/common/platforms/arm/arm_nsleep100_m4.S b/src/common/platforms/arm/arm_nsleep100_m4.S index d40820f6..afb596fa 100644 --- a/src/common/platforms/arm/arm_nsleep100_m4.S +++ b/src/common/platforms/arm/arm_nsleep100_m4.S @@ -26,7 +26,11 @@ .global mgos_nsleep100_impl .global mgos_nsleep100_loop_count +#ifdef STM32 +.section .RamFunc.mgos_nsleep100_impl +#else .section .iram.mgos_nsleep100_impl +#endif .type mgos_nsleep100_impl, %function .align 4 @@ -34,7 +38,11 @@ mgos_nsleep100_impl: ldr r3, =mgos_nsleep100_loop_count ldr r3, [r3] mul r0, r3 +#ifdef STM32L4 + mov r1, #3 +#else mov r1, #6 +#endif udiv r0, r0, r1 cbz r0, xxx lxx: diff --git a/src/common/platforms/arm/arm_nsleep100_m7.S b/src/common/platforms/arm/arm_nsleep100_m7.S index c0e8f490..36c878fd 100644 --- a/src/common/platforms/arm/arm_nsleep100_m7.S +++ b/src/common/platforms/arm/arm_nsleep100_m7.S @@ -26,7 +26,11 @@ .global mgos_nsleep100_impl .global mgos_nsleep100_loop_count +#ifdef STM32 +.section .RamFunc.mgos_nsleep100_impl +#else .section .iram.mgos_nsleep100_impl +#endif .type mgos_nsleep100_impl, %function .align 4 diff --git a/src/common/platforms/esp/stub_flasher.c b/src/common/platforms/esp/stub_flasher.c index d733d359..51f1ffa7 100644 --- a/src/common/platforms/esp/stub_flasher.c +++ b/src/common/platforms/esp/stub_flasher.c @@ -25,7 +25,7 @@ #if defined(ESP8266) #include "eagle_soc.h" #include "ets_sys.h" -#include "../../../miniz.c" +#include "miniz.c" #elif defined(ESP32) #include "rom/efuse.h" #include "rom/miniz.h" @@ -38,7 +38,7 @@ #include "uart.h" /* Param: baud rate. */ -uint32_t params[1] __attribute__((section(".params"))); +uint32_t params[2] __attribute__((section(".params"))); #define FLASH_BLOCK_SIZE 65536 #define FLASH_SECTOR_SIZE 4096 @@ -470,6 +470,27 @@ uint8_t cmd_loop(void) { resp = 0; break; } + case CMD_READ_REG: { + len = SLIP_recv(args, sizeof(args)); + if (len == 4) { + uint32_t value = READ_PERI_REG((uint32_t *) args[0]); + SLIP_send(&value, sizeof(value)); + resp = 0; + } else { + resp = 0x91; + } + break; + } + case CMD_WRITE_REG: { + len = SLIP_recv(args, sizeof(args)); + if (len == 8) { + WRITE_PERI_REG((uint32_t *) args[0], args[1]); + resp = 0; + } else { + resp = 0xa1; + } + break; + } } SLIP_send(&resp, 1); } while (cmd != CMD_BOOT_FW && cmd != CMD_REBOOT); @@ -477,8 +498,7 @@ uint8_t cmd_loop(void) { } void stub_main1(void) { - uint32_t baud_rate = params[0]; - uint32_t greeting = 0x4941484f; /* OHAI */ + uint32_t old_baud_rate = params[0], new_baud_rate = params[1]; uint8_t last_cmd; /* This points at us right now, reset for next boot. */ @@ -487,6 +507,7 @@ void stub_main1(void) { /* Selects SPI functions for flash pins. */ #if defined(ESP8266) SelectSpiFunction(); + spi_flash_attach(); SET_PERI_REG_MASK(0x3FF00014, 1); /* Switch to 160 MHz */ #elif defined(ESP32) esp_rom_spiflash_attach(ets_efuse_get_spiconfig(), 0 /* legacy */); @@ -496,9 +517,10 @@ void stub_main1(void) { 0 /* deviceId */, 16 * 1024 * 1024 /* chip_size */, FLASH_BLOCK_SIZE, FLASH_SECTOR_SIZE, FLASH_PAGE_SIZE, 0xffff /* status_mask */); - if (baud_rate > 0) { + uint32_t old_div = 0; + if (new_baud_rate > 0) { ets_delay_us(10000); - set_baud_rate(0, baud_rate); + old_div = set_baud_rate(0, old_baud_rate, new_baud_rate); } /* Give host time to get ready too. */ @@ -509,7 +531,7 @@ void stub_main1(void) { WRITE_PERI_REG(UART_FIFO_REG(0), 0x55); } #else - SLIP_send(&greeting, 4); + SLIP_send(&old_div, 4); #endif last_cmd = cmd_loop(); @@ -525,7 +547,7 @@ void stub_main1(void) { * then jumps to 0x4000108a, then checks strapping bits again (which will * not have changed), and then proceeds to 0x400010a8. */ - volatile uint32_t *sp = &baud_rate; + volatile uint32_t *sp = &old_baud_rate; while (*sp != (uint32_t) 0x40001100) sp++; *sp = 0x400010a8; /* diff --git a/src/common/platforms/esp/stub_flasher.h b/src/common/platforms/esp/stub_flasher.h index 4bae4c6a..dd1f948b 100644 --- a/src/common/platforms/esp/stub_flasher.h +++ b/src/common/platforms/esp/stub_flasher.h @@ -91,6 +91,24 @@ enum stub_cmd { * Output: arguments. */ CMD_ECHO = 8, + + /* + * Read register value. + * + * Args: register address. + * Input: None. + * Output: register value. + */ + CMD_READ_REG = 9, + + /* + * Write register value. + * + * Args: register address, value. + * Input: None. + * Output: None. + */ + CMD_WRITE_REG = 10, }; #endif /* CS_COMMON_PLATFORMS_ESP8266_STUBS_STUB_FLASHER_H_ */ diff --git a/src/common/platforms/esp32/stubs/stub.ld b/src/common/platforms/esp32/stubs/stub.ld index 6c2af65a..84085bb3 100644 --- a/src/common/platforms/esp32/stubs/stub.ld +++ b/src/common/platforms/esp32/stubs/stub.ld @@ -42,5 +42,10 @@ SECTIONS { INCLUDE "components/esp32/ld/esp32.rom.ld" INCLUDE "components/esp32/ld/esp32.rom.spiram_incompatible_fns.ld" +PROVIDE(ets_isr_mask = 0x400067fc); +PROVIDE(ets_isr_unmask = 0x40006808); +PROVIDE(MD5Init = 0x4005da7c); +PROVIDE(MD5Update = 0x4005da9c); +PROVIDE(MD5Final = 0x4005db1c); PROVIDE(esp_rom_spiflash_attach = 0x40062a6c); PROVIDE(esp_rom_spiflash_config_clk = 0x40062bc8); diff --git a/src/common/platforms/esp32/stubs/uart.c b/src/common/platforms/esp32/stubs/uart.c index 8d2bca74..d6d1faad 100644 --- a/src/common/platforms/esp32/stubs/uart.c +++ b/src/common/platforms/esp32/stubs/uart.c @@ -19,9 +19,13 @@ #include "rom_functions.h" -void set_baud_rate(uint32_t uart_no, uint32_t baud_rate) { - uint32_t master_freq = ets_get_detected_xtal_freq() << 4; - master_freq += (baud_rate / 2); - uint32_t div = master_freq / baud_rate; - uart_div_modify(uart_no, div); +uint32_t set_baud_rate(uint32_t uart_no, uint32_t old_baud_rate, + uint32_t new_baud_rate) { + uint32_t uart_reg = REG_READ(UART_CLKDIV_REG(uart_no)); + uint32_t uart_div = uart_reg & UART_CLKDIV_M; + uint32_t fraction = (uart_reg >> UART_CLKDIV_FRAG_S) & UART_CLKDIV_FRAG_V; + uart_div = (uart_div << 4) + fraction; + uint32_t master_freq = uart_div * old_baud_rate; + uart_div_modify(uart_no, master_freq / new_baud_rate); + return uart_div; } diff --git a/src/common/platforms/esp32/stubs/uart.h b/src/common/platforms/esp32/stubs/uart.h index b28edf88..99e351ac 100644 --- a/src/common/platforms/esp32/stubs/uart.h +++ b/src/common/platforms/esp32/stubs/uart.h @@ -20,6 +20,7 @@ #include -void set_baud_rate(uint32_t uart_no, uint32_t baud_rate); +uint32_t set_baud_rate(uint32_t uart_no, uint32_t old_baud_rate, + uint32_t new_baud_rate); #endif /* CS_COMMON_PLATFORMS_ESP32_STUBS_UART_H_ */ diff --git a/src/common/platforms/esp8266/stubs/Makefile b/src/common/platforms/esp8266/stubs/Makefile index 7aee5613..f7a76122 100644 --- a/src/common/platforms/esp8266/stubs/Makefile +++ b/src/common/platforms/esp8266/stubs/Makefile @@ -23,13 +23,13 @@ all: $(STUB_ELF) $(STUB_ELF): $(STUB) $(LIBS) @echo " CC $^ -> $@" @[ -d $(BUILD_DIR) ] || mkdir $(BUILD_DIR) - @docker run --rm -i -v $(CURDIR)/../../../..:/src $(SDK) //bin/bash -c \ + docker run --rm -i -v $(CURDIR)/../../../..:/src $(SDK) //bin/bash -c \ "cd /src/common/platforms/esp8266/stubs && \ $(XT_CC) -std=c99 -Wall -Werror -Os -DESP8266 \ -mtext-section-literals -mlongcalls -nostdlib -fno-builtin \ - -I. -I/src/common/platforms/esp \ + -I. -I/src/common/platforms/esp -I/src/common/platforms/esp8266 \ -I/opt/Espressif/ESP8266_SDK \ - -Wl,-static -ffunction-sections -Wl,--gc-sections \ + -Wl,-static -ffunction-sections -lgcc -Wl,--gc-sections \ -Tstub.ld $(CFLAGS) -o $@ $^" wrap: $(STUB_JSON) diff --git a/src/common/platforms/esp8266/stubs/stub.ld b/src/common/platforms/esp8266/stubs/stub.ld index f54a4f84..08c416c4 100644 --- a/src/common/platforms/esp8266/stubs/stub.ld +++ b/src/common/platforms/esp8266/stubs/stub.ld @@ -41,3 +41,4 @@ INCLUDE "eagle.rom.addr.v6.ld" PROVIDE(SPIFlashModeConfig = 0x40004568); PROVIDE(SPI_erase_sector = 0x400040c0); PROVIDE(SPI_erase_block = 0x40004120); +PROVIDE(uart_div_modify = 0x400039d8); diff --git a/src/common/platforms/esp8266/stubs/uart.c b/src/common/platforms/esp8266/stubs/uart.c index cb99b2d9..39e28117 100644 --- a/src/common/platforms/esp8266/stubs/uart.c +++ b/src/common/platforms/esp8266/stubs/uart.c @@ -18,9 +18,15 @@ #include "uart.h" #include "ets_sys.h" -#define UART_CLKDIV_26MHZ(B) (52000000 + B / 2) / B +#define UART_CLKDIV_M (UART_CLKDIV_CNT << UART_CLKDIV_S) -void set_baud_rate(uint32_t uart_no, uint32_t baud_rate) { - uint32_t div = UART_CLKDIV_26MHZ(baud_rate); - WRITE_PERI_REG(UART_CLKDIV_REG(uart_no), div & 0xfffff); +void uart_div_modify(uint8_t uart_no, uint32_t div); + +uint32_t set_baud_rate(uint32_t uart_no, uint32_t old_baud_rate, + uint32_t new_baud_rate) { + uint32_t uart_reg = READ_PERI_REG(UART_CLKDIV_REG(uart_no)); + uint32_t uart_div = uart_reg & UART_CLKDIV_M; + uint32_t master_freq = uart_div * old_baud_rate; + uart_div_modify(uart_no, master_freq / new_baud_rate); + return uart_div; } diff --git a/src/common/platforms/esp8266/stubs/uart.h b/src/common/platforms/esp8266/stubs/uart.h index ad3ef1dc..5afd9e38 100644 --- a/src/common/platforms/esp8266/stubs/uart.h +++ b/src/common/platforms/esp8266/stubs/uart.h @@ -20,9 +20,11 @@ #include -void set_baud_rate(uint32_t uart_no, uint32_t baud_rate); +#include "uart_register.h" + +uint32_t set_baud_rate(uint32_t uart_no, uint32_t old_baud_rate, + uint32_t new_baud_rate); -#define REG_UART_BASE(i) (0x60000000 + (i) *0xf00) #define UART_FIFO_REG(i) (REG_UART_BASE(i) + 0x0) #define UART_CONF1_REG(i) (REG_UART_BASE(i) + 0x24) #define UART_RX_TOUT_EN (BIT(31)) diff --git a/src/common/platforms/esp8266/tools/malloc_analyzer.py b/src/common/platforms/esp8266/tools/malloc_analyzer.py deleted file mode 100644 index 91d4d0c4..00000000 --- a/src/common/platforms/esp8266/tools/malloc_analyzer.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/python - -# Analyzers output of firmware compiler with -DESP_ENABLE_MALLOC_TRACES - -import re -import sys - -allocs = {} - -for l in sys.stdin: - m = re.match(r'(ca|ma|re|fr) (\S+)\s*(\S*)\s*(\S*)', l) - if not m: continue - op = m.group(1) - if op in ('ca', 'ma'): - allocs[m.group(2)] = long(m.group(3)) - else: - if m.group(2) in allocs: - del allocs[m.group(2)] - if op == 're': - allocs[m.group(3)] = long(m.group(4)) - -for k, v in sorted(allocs.iteritems()): - print k, v diff --git a/src/common/platforms/lwip/mg_lwip_ev_mgr.c b/src/common/platforms/lwip/mg_lwip_ev_mgr.c index b495464e..ab8f19f7 100644 --- a/src/common/platforms/lwip/mg_lwip_ev_mgr.c +++ b/src/common/platforms/lwip/mg_lwip_ev_mgr.c @@ -67,7 +67,6 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) { break; } case MG_SIG_CLOSE_CONN: { - nc->flags |= MG_F_SEND_AND_CLOSE; mg_close_conn(nc); break; } diff --git a/src/common/platforms/lwip/mg_lwip_net_if.c b/src/common/platforms/lwip/mg_lwip_net_if.c index d0777029..e78d93ce 100644 --- a/src/common/platforms/lwip/mg_lwip_net_if.c +++ b/src/common/platforms/lwip/mg_lwip_net_if.c @@ -581,7 +581,7 @@ struct tcp_recved_ctx { void tcp_recved_tcpip(void *arg) { struct tcp_recved_ctx *ctx = (struct tcp_recved_ctx *) arg; - tcp_recved(ctx->tpcb, ctx->len); + if (ctx->tpcb != NULL) tcp_recved(ctx->tpcb, ctx->len); } static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf, diff --git a/src/common/platforms/simplelink/cs_simplelink.h b/src/common/platforms/simplelink/cs_simplelink.h index 2b415410..71af2dcb 100644 --- a/src/common/platforms/simplelink/cs_simplelink.h +++ b/src/common/platforms/simplelink/cs_simplelink.h @@ -169,7 +169,7 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc); #endif /* SL_MAJOR_VERSION_NUM < 2 */ -int slfs_open(const unsigned char *fname, uint32_t flags); +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token); #endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */ diff --git a/src/common/platforms/simplelink/sl_fs_slfs.c b/src/common/platforms/simplelink/sl_fs_slfs.c index 8170310b..7dcff308 100644 --- a/src/common/platforms/simplelink/sl_fs_slfs.c +++ b/src/common/platforms/simplelink/sl_fs_slfs.c @@ -31,14 +31,14 @@ #include "common/mg_mem.h" #if SL_MAJOR_VERSION_NUM < 2 -int slfs_open(const unsigned char *fname, uint32_t flags) { +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) { _i32 fh; - _i32 r = sl_FsOpen(fname, flags, NULL /* token */, &fh); + _i32 r = sl_FsOpen(fname, flags, (unsigned long *) token, &fh); return (r < 0 ? r : fh); } #else /* SL_MAJOR_VERSION_NUM >= 2 */ -int slfs_open(const unsigned char *fname, uint32_t flags) { - return sl_FsOpen(fname, flags, NULL /* token */); +int slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) { + return sl_FsOpen(fname, flags, (unsigned long *) token); } #endif @@ -54,9 +54,11 @@ const char *drop_dir(const char *fname, bool *is_slfs); #define FS_SLFS_MAX_FILE_SIZE (64 * 1024) #endif -struct sl_file_size_hint { +struct sl_file_open_info { char *name; size_t size; + uint32_t flags; + uint32_t *token; }; struct sl_fd_info { @@ -66,7 +68,10 @@ struct sl_fd_info { }; static struct sl_fd_info s_sl_fds[MAX_OPEN_SLFS_FILES]; -static struct sl_file_size_hint s_sl_file_size_hints[MAX_OPEN_SLFS_FILES]; +static struct sl_file_open_info s_sl_file_open_infos[MAX_OPEN_SLFS_FILES]; + +static struct sl_file_open_info *fs_slfs_find_foi(const char *name, + bool create); static int sl_fs_to_errno(_i32 r) { DBG(("SL error: %d", (int) r)); @@ -107,7 +112,13 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) { _u32 am = 0; fi->size = (size_t) -1; int rw = (flags & 3); - size_t new_size = FS_SLFS_MAX_FILE_SIZE; + size_t new_size = 0; + struct sl_file_open_info *foi = + fs_slfs_find_foi(pathname, false /* create */); + if (foi != NULL) { + LOG(LL_DEBUG, ("FOI for %s: %d 0x%x %p", pathname, (int) foi->size, + (unsigned int) foi->flags, foi->token)); + } if (rw == O_RDONLY) { SlFsFileInfo_t sl_fi; _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi); @@ -122,24 +133,27 @@ int fs_slfs_open(const char *pathname, int flags, mode_t mode) { return set_errno(ENOTSUP); } if (flags & O_CREAT) { - size_t i; - for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { - if (s_sl_file_size_hints[i].name != NULL && - strcmp(s_sl_file_size_hints[i].name, pathname) == 0) { - new_size = s_sl_file_size_hints[i].size; - MG_FREE(s_sl_file_size_hints[i].name); - s_sl_file_size_hints[i].name = NULL; - break; - } + if (foi->size > 0) { + new_size = foi->size; + } else { + new_size = FS_SLFS_MAX_FILE_SIZE; } am = FS_MODE_OPEN_CREATE(new_size, 0); } else { am = SL_FS_WRITE; } +#if SL_MAJOR_VERSION_NUM >= 2 + am |= SL_FS_OVERWRITE; +#endif } - fi->fh = slfs_open((_u8 *) pathname, am); - LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x) sz %u = %d", pathname, (int) am, - (unsigned int) new_size, (int) fi->fh)); + uint32_t *token = NULL; + if (foi != NULL) { + am |= foi->flags; + token = foi->token; + } + fi->fh = slfs_open((_u8 *) pathname, am, token); + LOG(LL_DEBUG, ("sl_FsOpen(%s, 0x%x, %p) sz %u = %d", pathname, (int) am, + token, (unsigned int) new_size, (int) fi->fh)); int r; if (fi->fh >= 0) { fi->pos = 0; @@ -243,16 +257,46 @@ int fs_slfs_rename(const char *from, const char *to) { return set_errno(ENOTSUP); } -void fs_slfs_set_new_file_size(const char *name, size_t size) { - int i; +static struct sl_file_open_info *fs_slfs_find_foi(const char *name, + bool create) { + int i = 0; for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { - if (s_sl_file_size_hints[i].name == NULL) { - DBG(("File size hint: %s %d", name, (int) size)); - s_sl_file_size_hints[i].name = strdup(name); - s_sl_file_size_hints[i].size = size; + if (s_sl_file_open_infos[i].name != NULL && + strcmp(drop_dir(s_sl_file_open_infos[i].name, NULL), name) == 0) { break; } } + if (i != MAX_OPEN_SLFS_FILES) return &s_sl_file_open_infos[i]; + if (!create) return NULL; + for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) { + if (s_sl_file_open_infos[i].name == NULL) break; + } + if (i == MAX_OPEN_SLFS_FILES) { + i = 0; /* Evict a random slot. */ + } + if (s_sl_file_open_infos[i].name != NULL) { + free(s_sl_file_open_infos[i].name); + } + s_sl_file_open_infos[i].name = strdup(name); + return &s_sl_file_open_infos[i]; +} + +void fs_slfs_set_file_size(const char *name, size_t size) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */); + foi->size = size; +} + +void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */); + foi->flags = flags; + foi->token = token; +} + +void fs_slfs_unset_file_flags(const char *name) { + struct sl_file_open_info *foi = fs_slfs_find_foi(name, false /* create */); + if (foi == NULL) return; + free(foi->name); + memset(foi, 0, sizeof(*foi)); } #endif /* defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS) */ diff --git a/src/common/platforms/simplelink/sl_fs_slfs.h b/src/common/platforms/simplelink/sl_fs_slfs.h index eb909c1b..4e4cb57b 100644 --- a/src/common/platforms/simplelink/sl_fs_slfs.h +++ b/src/common/platforms/simplelink/sl_fs_slfs.h @@ -39,7 +39,9 @@ off_t fs_slfs_lseek(int fd, off_t offset, int whence); int fs_slfs_unlink(const char *filename); int fs_slfs_rename(const char *from, const char *to); -void fs_slfs_set_new_file_size(const char *name, size_t size); +void fs_slfs_set_file_size(const char *name, size_t size); +void fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token); +void fs_slfs_unset_file_flags(const char *name); #endif /* defined(MG_FS_SLFS) */ diff --git a/src/common/platforms/simplelink/sl_ssl_if.c b/src/common/platforms/simplelink/sl_ssl_if.c index 6e4daa6e..92453e3d 100644 --- a/src/common/platforms/simplelink/sl_ssl_if.c +++ b/src/common/platforms/simplelink/sl_ssl_if.c @@ -115,9 +115,9 @@ bool pem_to_der(const char *pem_file, const char *der_file) { pf = fopen(pem_file, "r"); if (pf == NULL) goto clean; remove(der_file); - fs_slfs_set_new_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, - 2048); + fs_slfs_set_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, 2048); df = fopen(der_file, "w"); + fs_slfs_unset_file_flags(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN); if (df == NULL) goto clean; while (1) { char pem_buf[70]; diff --git a/src/common/platforms/stm32/flasher.go b/src/common/platforms/stm32/flasher.go index 8ddde035..36cbad11 100644 --- a/src/common/platforms/stm32/flasher.go +++ b/src/common/platforms/stm32/flasher.go @@ -11,7 +11,7 @@ import ( "time" "unsafe" - "cesanta.com/mos/flash/common" + "cesanta.com/common/go/fwbundle" "github.com/cesanta/errors" ) @@ -20,7 +20,7 @@ type FlashOpts struct { Timeout time.Duration } -func Flash(fw *common.FirmwareBundle, opts *FlashOpts) error { +func Flash(fw *fwbundle.FirmwareBundle, opts *FlashOpts) error { data, err := fw.GetPartData("boot") if err != nil { return errors.Annotatef(err, "invalid manifest") diff --git a/src/mg_common.h b/src/mg_common.h index 69900255..94a77232 100644 --- a/src/mg_common.h +++ b/src/mg_common.h @@ -20,7 +20,7 @@ #ifndef CS_MONGOOSE_SRC_COMMON_H_ #define CS_MONGOOSE_SRC_COMMON_H_ -#define MG_VERSION "6.13" +#define MG_VERSION "6.14" /* Local tweaks, applied before any of Mongoose's own headers. */ #ifdef MG_LOCALS diff --git a/src/mg_http.c b/src/mg_http.c index f8161a4b..5cd6f623 100644 --- a/src/mg_http.c +++ b/src/mg_http.c @@ -140,6 +140,7 @@ struct mg_http_multipart_stream { void *user_data; enum mg_http_multipart_stream_state state; int processing_part; + int data_avail; }; struct mg_reverse_proxy_data { @@ -758,9 +759,9 @@ void mg_http_handler(struct mg_connection *nc, int ev, if (io->len > 0 && (req_len = mg_parse_http(io->buf, io->len, hm, is_req)) > 0) { /* - * For HTTP messages without Content-Length, always send HTTP message - * before MG_EV_CLOSE message. - */ + * For HTTP messages without Content-Length, always send HTTP message + * before MG_EV_CLOSE message. + */ int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; hm->message.len = io->len; hm->body.len = io->buf + io->len - hm->body.p; @@ -768,6 +769,9 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_http_call_endpoint_handler(nc, ev2, hm); } pd->rcvd = 0; + if (pd->endpoint_handler != NULL && pd->endpoint_handler != nc->handler) { + mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL); + } } #if MG_ENABLE_FILESYSTEM @@ -778,17 +782,24 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_call(nc, nc->handler, nc->user_data, ev, ev_data); +#if MG_ENABLE_HTTP_STREAMING_MULTIPART + if (pd->mp_stream.boundary != NULL && + (ev == MG_EV_RECV || ev == MG_EV_POLL)) { + if (ev == MG_EV_RECV) { + pd->rcvd += *(int *) ev_data; + mg_http_multipart_continue(nc); + } else if (pd->mp_stream.data_avail) { + /* Try re-delivering the data. */ + mg_http_multipart_continue(nc); + } + return; + } +#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ + if (ev == MG_EV_RECV) { struct mg_str *s; pd->rcvd += *(int *) ev_data; -#if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (pd->mp_stream.boundary != NULL) { - mg_http_multipart_continue(nc); - return; - } -#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ - again: req_len = mg_parse_http(io->buf, io->len, hm, is_req); @@ -816,16 +827,23 @@ void mg_http_handler(struct mg_connection *nc, int ev, /* Do nothing, request is not yet fully buffered */ } #if MG_ENABLE_HTTP_WEBSOCKET - else if (nc->listener == NULL && - mg_get_http_header(hm, "Sec-WebSocket-Accept")) { + else if (nc->listener == NULL && (nc->flags & MG_F_IS_WEBSOCKET)) { /* We're websocket client, got handshake response from server. */ - /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ - mbuf_remove(io, req_len); - nc->proto_handler = mg_ws_handler; - nc->flags |= MG_F_IS_WEBSOCKET; - mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, - NULL); - mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); + DBG(("%p WebSocket upgrade code %d", nc, hm->resp_code)); + if (hm->resp_code == 101 && + mg_get_http_header(hm, "Sec-WebSocket-Accept")) { + /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ + mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, + hm); + mbuf_remove(io, req_len); + nc->proto_handler = mg_ws_handler; + mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); + } else { + mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, + hm); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + mbuf_remove(io, req_len); + } } else if (nc->listener != NULL && (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) { struct mg_http_endpoint *ep; @@ -856,7 +874,7 @@ void mg_http_handler(struct mg_connection *nc, int ev, mg_ws_handshake(nc, vec, hm); } mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, - NULL); + hm); mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); } } @@ -983,8 +1001,9 @@ exit_mp: #define CONTENT_DISPOSITION "Content-Disposition: " -static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, - const char *data, size_t data_len) { +static size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev, + const char *data, + size_t data_len) { struct mg_http_multipart_part mp; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); memset(&mp, 0, sizeof(mp)); @@ -994,8 +1013,11 @@ static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, mp.user_data = pd->mp_stream.user_data; mp.data.p = data; mp.data.len = data_len; + mp.num_data_consumed = data_len; mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp); pd->mp_stream.user_data = mp.user_data; + pd->mp_stream.data_avail = (mp.num_data_consumed != data_len); + return mp.num_data_consumed; } static int mg_http_multipart_finalize(struct mg_connection *c) { @@ -1133,19 +1155,25 @@ static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) { boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); if (boundary == NULL) { - int data_size = (io->len - (pd->mp_stream.boundary_len + 6)); - if (data_size > 0) { - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, - data_size); - mbuf_remove(io, data_size); + int data_len = (io->len - (pd->mp_stream.boundary_len + 6)); + if (data_len > 0) { + size_t consumed = mg_http_multipart_call_handler( + c, MG_EV_HTTP_PART_DATA, io->buf, (size_t) data_len); + mbuf_remove(io, consumed); } return 0; } else if (boundary != NULL) { - int data_size = (boundary - io->buf - 4); - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size); - mbuf_remove(io, (boundary - io->buf)); - pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; - return 1; + size_t data_len = ((size_t)(boundary - io->buf) - 4); + size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, + io->buf, data_len); + mbuf_remove(io, consumed); + if (consumed == data_len) { + mbuf_remove(io, 4); + pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; + return 1; + } else { + return 0; + } } else { return 0; } @@ -1923,7 +1951,7 @@ int mg_check_digest_auth(struct mg_str method, struct mg_str uri, struct mg_str nc, struct mg_str nonce, struct mg_str auth_domain, FILE *fp) { char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)]; - char expected_response[33]; + char exp_resp[33]; /* * Read passwords file line by line. If should have htdigest format, @@ -1937,11 +1965,10 @@ int mg_check_digest_auth(struct mg_str method, struct mg_str uri, /* Username and domain matched, check the password */ mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1), nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len, - qop.p, qop.len, expected_response); - LOG(LL_DEBUG, - ("%.*s %s %.*s %s", (int) username.len, username.p, f_domain, - (int) response.len, response.p, expected_response)); - return mg_ncasecmp(response.p, expected_response, response.len) == 0; + qop.p, qop.len, exp_resp); + LOG(LL_DEBUG, ("%.*s %s %.*s %s", (int) username.len, username.p, + f_domain, (int) response.len, response.p, exp_resp)); + return mg_ncasecmp(response.p, exp_resp, strlen(exp_resp)) == 0; } } @@ -2536,9 +2563,11 @@ void mg_http_send_digest_auth_request(struct mg_connection *c, domain, (unsigned long) mg_time()); } -static void mg_http_send_options(struct mg_connection *nc) { +static void mg_http_send_options(struct mg_connection *nc, + struct mg_serve_http_opts *opts) { + mg_send_response_line(nc, 200, opts->extra_headers); mg_printf(nc, "%s", - "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS" + "Allow: GET, POST, HEAD, CONNECT, OPTIONS" #if MG_ENABLE_HTTP_WEBDAV ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2" #endif @@ -2645,7 +2674,7 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, #endif #endif /* MG_ENABLE_HTTP_WEBDAV */ } else if (!mg_vcmp(&hm->method, "OPTIONS")) { - mg_http_send_options(nc); + mg_http_send_options(nc, opts); } else if (is_directory && index_file == NULL) { #if MG_ENABLE_DIRECTORY_LISTING if (strcmp(opts->enable_directory_listing, "yes") == 0) { diff --git a/src/mg_http.h b/src/mg_http.h index c89d38f3..3be7656f 100644 --- a/src/mg_http.h +++ b/src/mg_http.h @@ -80,6 +80,14 @@ struct mg_http_multipart_part { struct mg_str data; int status; /* <0 on error */ void *user_data; + /* + * User handler can indicate how much of the data was consumed + * by setting this variable. By default, it is assumed that all + * data has been consumed by the handler. + * If not all data was consumed, user's handler will be invoked again later + * with the remainder. + */ + size_t num_data_consumed; }; /* SSI call context */ @@ -98,7 +106,7 @@ struct mg_ssi_call_ctx { #if MG_ENABLE_HTTP_WEBSOCKET #define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* struct http_message * */ -#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* NULL */ +#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* struct http_message * */ #define MG_EV_WEBSOCKET_FRAME 113 /* struct websocket_message * */ #define MG_EV_WEBSOCKET_CONTROL_FRAME 114 /* struct websocket_message * */ #endif @@ -137,7 +145,9 @@ struct mg_ssi_call_ctx { * - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received the WebSocket * handshake request. `ev_data` contains parsed HTTP request. * - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed the WebSocket - * handshake. `ev_data` is `NULL`. + * handshake. `ev_data` is a `struct http_message` containing the + * client's request (server mode) or server's response (client). + * In client mode handler can examine `resp_code`, which should be 101. * - MG_EV_WEBSOCKET_FRAME: new WebSocket frame has arrived. `ev_data` is * `struct websocket_message *` * diff --git a/src/mg_http_websocket.c b/src/mg_http_websocket.c index e69e4b73..317b4997 100644 --- a/src/mg_http_websocket.c +++ b/src/mg_http_websocket.c @@ -478,6 +478,8 @@ void mg_send_websocket_handshake3v(struct mg_connection *nc, } mg_printf(nc, "\r\n"); + nc->flags |= MG_F_IS_WEBSOCKET; + mbuf_free(&auth); } diff --git a/src/mg_internal.h b/src/mg_internal.h index 20614234..0e62448e 100644 --- a/src/mg_internal.h +++ b/src/mg_internal.h @@ -34,7 +34,9 @@ #include "mg_http.h" #include "mg_net.h" +#ifndef MG_CTL_MSG_MESSAGE_SIZE #define MG_CTL_MSG_MESSAGE_SIZE 8192 +#endif /* internals that need to be accessible in unit tests */ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc, diff --git a/src/mg_mqtt.c b/src/mg_mqtt.c index 5c0b194e..68069e8d 100644 --- a/src/mg_mqtt.c +++ b/src/mg_mqtt.c @@ -196,26 +196,44 @@ static void mg_mqtt_proto_data_destructor(void *proto_data) { MG_FREE(proto_data); } +static struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) { + struct mg_str res = *topic; + const char *c = mg_strchr(*topic, '/'); + if (c != NULL) { + res.len = (c - topic->p); + topic->len -= (res.len + 1); + topic->p += (res.len + 1); + } else { + topic->len = 0; + } + return res; +} + +/* Refernce: https://mosquitto.org/man/mqtt-7.html */ int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) { - /* TODO(mkm): implement real matching */ - if (memchr(exp.p, '#', exp.len)) { - /* exp `foo/#` will become `foo/` */ - exp.len -= 1; - /* - * topic should be longer than the expression: e.g. topic `foo/bar` does - * match `foo/#`, but neither `foo` nor `foo/` do. - */ - if (topic.len <= exp.len) { + struct mg_str ec, tc; + if (exp.len == 0) return 0; + while (1) { + ec = mg_mqtt_next_topic_component(&exp); + tc = mg_mqtt_next_topic_component(&topic); + if (ec.len == 0) { + if (tc.len != 0) return 0; + if (exp.len == 0) break; + continue; + } + if (mg_vcmp(&ec, "+") == 0) { + if (tc.len == 0 && topic.len == 0) return 0; + continue; + } + if (mg_vcmp(&ec, "#") == 0) { + /* Must be the last component in the expression or it's invalid. */ + return (exp.len == 0); + } + if (mg_strcmp(ec, tc) != 0) { return 0; } - - /* Truncate topic so that it'll pass the next length check */ - topic.len = exp.len; } - if (topic.len != exp.len) { - return 0; - } - return strncmp(topic.p, exp.p, exp.len) == 0; + return (tc.len == 0 && topic.len == 0); } int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) { diff --git a/src/mg_net.c b/src/mg_net.c index 97afe9ab..3b1b7038 100644 --- a/src/mg_net.c +++ b/src/mg_net.c @@ -128,8 +128,16 @@ MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) { static int mg_do_recv(struct mg_connection *nc); int mg_if_poll(struct mg_connection *nc, double now) { - if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) || - (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) { + if (nc->flags & MG_F_CLOSE_IMMEDIATELY) { + mg_close_conn(nc); + return 0; + } else if (nc->flags & MG_F_SEND_AND_CLOSE) { + if (nc->send_mbuf.len == 0) { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + mg_close_conn(nc); + return 0; + } + } else if (nc->flags & MG_F_RECV_AND_CLOSE) { mg_close_conn(nc); return 0; } @@ -172,6 +180,13 @@ void mg_destroy_conn(struct mg_connection *conn, int destroy_if) { } void mg_close_conn(struct mg_connection *conn) { + /* See if there's any remaining data to deliver. Skip if user completely + * throttled the connection there will be no progress anyway. */ + if (conn->sock != INVALID_SOCKET && mg_do_recv(conn) == -2) { + /* Receive is throttled, wait. */ + conn->flags |= MG_F_RECV_AND_CLOSE; + return; + } #if MG_ENABLE_SSL if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) { mg_ssl_if_conn_close_notify(conn); @@ -262,6 +277,7 @@ void mg_mgr_free(struct mg_mgr *m) { for (conn = m->active_connections; conn != NULL; conn = tmp_conn) { tmp_conn = conn->next; + conn->flags |= MG_F_CLOSE_IMMEDIATELY; mg_close_conn(conn); } @@ -565,18 +581,23 @@ static int mg_do_recv(struct mg_connection *nc) { ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) { return -1; } - len = recv_avail_size(nc, len); - if (len == 0) return -2; - if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) { - mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len); - } - buf = nc->recv_mbuf.buf + nc->recv_mbuf.len; - len = nc->recv_mbuf.size - nc->recv_mbuf.len; - if (nc->flags & MG_F_UDP) { - res = mg_recv_udp(nc, buf, len); - } else { - res = mg_recv_tcp(nc, buf, len); - } + do { + len = recv_avail_size(nc, len); + if (len == 0) { + res = -2; + break; + } + if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) { + mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len); + } + buf = nc->recv_mbuf.buf + nc->recv_mbuf.len; + len = nc->recv_mbuf.size - nc->recv_mbuf.len; + if (nc->flags & MG_F_UDP) { + res = mg_recv_udp(nc, buf, len); + } else { + res = mg_recv_tcp(nc, buf, len); + } + } while (res > 0 && !(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_UDP))); return res; } @@ -693,7 +714,9 @@ static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) { mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV); } #endif - mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + if (n != 0) { + mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + } } out: diff --git a/src/mg_net.h b/src/mg_net.h index d854e770..cbe75b4c 100644 --- a/src/mg_net.h +++ b/src/mg_net.h @@ -140,6 +140,7 @@ struct mg_connection { #define MG_F_WANT_READ (1 << 6) /* SSL specific */ #define MG_F_WANT_WRITE (1 << 7) /* SSL specific */ #define MG_F_IS_WEBSOCKET (1 << 8) /* Websocket specific */ +#define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */ /* Flags that are settable by user */ #define MG_F_SEND_AND_CLOSE (1 << 10) /* Push remaining data and close */ @@ -225,7 +226,7 @@ int mg_mgr_poll(struct mg_mgr *mgr, int milli); * `func` callback function will be called by the IO thread for each * connection. When called, the event will be `MG_EV_POLL`, and a message will * be passed as the `ev_data` pointer. Maximum message size is capped - * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes. + * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes by default. */ void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data, size_t len); diff --git a/test/Makefile b/test/Makefile index 89223ab8..97f41c9a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -9,7 +9,9 @@ SRC_DIR = ../src COMMON_DIR_PUB = ../src/common COMMON_DIR_DEV = ../../common -ifneq ("$(wildcard ../../fw/defs)","") +# Our dev repo and public mongoose repo have different layouts, so here +# we make it work on both +ifneq ("$(wildcard ../../fw)","") COMMON_PARENT = ../.. else COMMON_PARENT = $(SRC_DIR) @@ -19,8 +21,6 @@ TEST_SOURCES = unit_test.c test_util.c test_main.c AMALGAMATED_SOURCES = ../mongoose.c KRYPTON_PATH = $(REPO_ROOT)/krypton -# Our dev repo and public mongoose repo have different layouts, so here -# we make it work on both VPATH = $(MONGOOSE_DIR) $(SRC_DIR) $(COMMON_DIR_PUB) $(COMMON_DIR_DEV) $(COMMON_PARENT) # or Krypton, or mbedTLS diff --git a/test/unit_test.c b/test/unit_test.c index cde8df8a..5901c21c 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -15,12 +15,12 @@ * license, as set out in . */ -#include "mongoose.h" -#include "src/mg_internal.h" #include "unit_test.h" +#include "common/cs_md5.h" #include "common/test_main.h" #include "common/test_util.h" -#include "common/cs_md5.h" +#include "mongoose.h" +#include "src/mg_internal.h" #if defined(__STDC_VERSION__) && __STDC_VERSION__ < 199901L && !defined(WIN32) #define __func__ "" @@ -2011,7 +2011,8 @@ static const char *test_http(void) { char buf[50] = "", status[100] = "", mime1[20] = "", mime2[100] = ""; char opt_buf[1024] = ""; const char *opt_answer = - "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS, MKCOL, " + "HTTP/1.1 200 OK\r\nServer: Mongoose/" MG_VERSION + "\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS, MKCOL, " "PUT, DELETE, PROPFIND, MOVE"; char url[1000]; @@ -2539,32 +2540,6 @@ static const char *test_http_range(void) { return NULL; } -static void cb3(struct mg_connection *nc, int ev, void *ev_data) { - struct websocket_message *wm = (struct websocket_message *) ev_data; - - if (ev == MG_EV_WEBSOCKET_FRAME) { - const char *reply = wm->size == 2 && !memcmp(wm->data, "hi", 2) ? "A" : "B"; - mg_printf_websocket_frame(nc, WEBSOCKET_OP_TEXT, "%s", reply); - } -} - -static void cb4(struct mg_connection *nc, int ev, void *ev_data) { - struct websocket_message *wm = (struct websocket_message *) ev_data; - - if (ev == MG_EV_WEBSOCKET_FRAME) { - memcpy(nc->user_data, wm->data, wm->size); - mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0); - } else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE) { - /* Send "hi" to server. server must reply "A". */ - struct mg_str h[2]; - h[0].p = "h"; - h[0].len = 1; - h[1].p = "i"; - h[1].len = 1; - mg_send_websocket_framev(nc, WEBSOCKET_OP_TEXT, h, 2); - } -} - static void cb_ws_server(struct mg_connection *nc, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; @@ -2779,6 +2754,38 @@ static const char *test_websocket(void) { return NULL; } +static void cb3(struct mg_connection *nc, int ev, void *ev_data) { + struct websocket_message *wm = (struct websocket_message *) ev_data; + if (ev != MG_EV_WEBSOCKET_FRAME) return; + DBG(("server data '%.*s'", (int) wm->size, wm->data)); + const char *reply = wm->size == 2 && !memcmp(wm->data, "hi", 2) ? "A" : "B"; + mg_printf_websocket_frame(nc, WEBSOCKET_OP_TEXT, "%s", reply); +} + +static void cb4(struct mg_connection *nc, int ev, void *ev_data) { + char *buf = (char *) nc->user_data; + if (ev == MG_EV_WEBSOCKET_FRAME) { + struct websocket_message *wm = (struct websocket_message *) ev_data; + DBG(("client data '%.*s'", (int) wm->size, wm->data)); + memcpy(buf, wm->data, wm->size); + mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, NULL, 0); + } else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_DONE) { + struct http_message *hm = (struct http_message *) ev_data; + DBG(("code %d", hm->resp_code)); + if (hm->resp_code == 101) { + /* Send "hi" to server. server must reply "A". */ + struct mg_str h[2]; + h[0].p = "h"; + h[0].len = 1; + h[1].p = "i"; + h[1].len = 1; + mg_send_websocket_framev(nc, WEBSOCKET_OP_TEXT, h, 2); + } else { + snprintf(buf, 20, "code %d", hm->resp_code); + } + } +} + static void cbwep(struct mg_connection *c, int ev, void *ev_data) { struct websocket_message *wm = (struct websocket_message *) ev_data; char *buf = (char *) c->user_data; @@ -2786,6 +2793,9 @@ static void cbwep(struct mg_connection *c, int ev, void *ev_data) { switch (ev) { case MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: strcat(buf, "R"); + if (buf[0] != '0') { + mg_http_send_error(c, 403, "I don't like you"); + } break; case MG_EV_WEBSOCKET_HANDSHAKE_DONE: strcat(buf, "D"); @@ -2802,7 +2812,7 @@ static const char *test_websocket_endpoint(void) { struct mg_mgr mgr; struct mg_connection *nc; const char *local_addr = "127.0.0.1:7798"; - char buf[20] = "", buf2[20] = ""; + char buf[20] = "", buf2[20] = "0"; mg_mgr_init(&mgr, NULL); /* mgr.hexdump_file = "-"; */ @@ -2817,10 +2827,21 @@ static const char *test_websocket_endpoint(void) { nc->user_data = buf; mg_send_websocket_handshake(nc, "/boo", NULL); poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); - mg_mgr_free(&mgr); - /* Check that test buffer has been filled by the callback properly. */ - ASSERT_STREQ(buf, "RDF|hi"); + ASSERT_STREQ(buf, "0RDF|hi"); + + /* Test handshake failure */ + ASSERT((nc = mg_connect(&mgr, local_addr, cb4)) != NULL); + mg_set_protocol_http_websocket(nc); + buf[0] = '\0'; + buf2[0] = '1'; + buf2[1] = '\0'; + nc->user_data = buf; + mg_send_websocket_handshake(nc, "/boo", NULL); + poll_until(&mgr, 1, c_str_ne, buf, (void *) ""); + ASSERT_STREQ(buf, "code 403"); + + mg_mgr_free(&mgr); return NULL; } @@ -3379,15 +3400,36 @@ static const char *test_mqtt_parse_mqtt_qos1(void) { } static const char *test_mqtt_match_topic_expression(void) { - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo")), 1); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/bar")), 0); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("/foo", mg_mk_str("/foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/foo", mg_mk_str("/foo"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foobar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo//"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/bar"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/+"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/bar", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/bar", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+/bar", mg_mk_str("foo//bar"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo/+/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+/#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/foo/bar", mg_mk_str("/foo/bar"))); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar")), 1); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar/baz")), - 1); + ASSERT(!mg_mqtt_vmatch_topic_expression("", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("/", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("/", mg_mk_str("/"))); + + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar/baz"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("#/foo", mg_mk_str("foo"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("#/foo", mg_mk_str("bar/foo"))); return NULL; } @@ -5665,7 +5707,7 @@ static const char *test_socks(void) { mbuf_resize(&c->recv_mbuf, 10000000); /* Run event loop. Use more cycles to let file download complete. */ - poll_until(&mgr, 10, c_str_ne, status, (void *) ""); + poll_until(&mgr, 15, c_str_ne, status, (void *) ""); ASSERT_STREQ(status, "success"); mg_mgr_free(&mgr);