From cb920a03d3d6079033cd30c1142978e7116cbbff Mon Sep 17 00:00:00 2001 From: sfeakes Date: Sat, 14 Dec 2024 08:17:00 -0600 Subject: [PATCH] Version 1.4.1 --- Makefile | 9 +- config.c | 29 + config.h | 31 +- extras/sprinklerDarkskys.sh | 51 - extras/sprinklerMeteohub.sh | 30 - extras/sprinklerOpenWeather.sh | 53 - extras/sprinklerRainProbability.sh | 121 +- hassio.c | 49 +- mongoose.c | 3465 ++++++++++++++-------------- mongoose.h | 553 +++-- net_services.c | 93 +- release/sprinklerd | Bin 196396 -> 211500 bytes release/sprinklerd.test.conf | 164 -- sd_cron.c | 45 +- sprinkler.c | 9 +- version.h | 2 +- zone_ctrl.c | 9 +- 17 files changed, 2379 insertions(+), 2334 deletions(-) delete mode 100755 extras/sprinklerDarkskys.sh delete mode 100755 extras/sprinklerMeteohub.sh delete mode 100755 extras/sprinklerOpenWeather.sh delete mode 100644 release/sprinklerd.test.conf diff --git a/Makefile b/Makefile index 74547aa..b402f31 100755 --- a/Makefile +++ b/Makefile @@ -49,7 +49,14 @@ GPIO = ./release/gpio # deleting dependencies appended to the file from 'make depend' # -.PHONY: depend clean +.PHONY: depend clean release + +release: + sudo docker run -it --mount type=bind,source=./,target=/build aqualinkd-releasebin make armhf + $(info Binaries for release have been built) + +armhf: CC = arm-linux-gnueabihf-gcc +armhf: $(MAIN) all: $(MAIN) @echo: $(MAIN) have been compiled diff --git a/config.c b/config.c index 7e7b355..f2b82e8 100644 --- a/config.c +++ b/config.c @@ -368,6 +368,7 @@ void readCfg(char *inifile) exit (EXIT_FAILURE); } + _sdconfig_.inputs = 0; // Caculate how many inputs we have for (i=1; i <= 24; i++) // 24 = Just some arbutary number (max GPIO without expansion board) { @@ -420,6 +421,34 @@ void readCfg(char *inifile) } } + _sdconfig_.runBeforeCmds = 0; + // Caculate how many external command to run + for (i=1; i <= 10; i++) // 10 = Just some arbutary number + { + sprintf(str, "EXTERNAL:%d", i); + pin = ini_getl(str, "RUNB4STARTTIME", -1, inifile); + if (pin == -1) + break; + else + _sdconfig_.runBeforeCmds = i; + } + + if ( _sdconfig_.runBeforeCmds != 0) { + // n= _sdconfig_.zones+1; + _sdconfig_.runBeforeCmd = malloc((_sdconfig_.runBeforeCmds) * sizeof(struct RunB4CalStart)); + for (i=0; i < _sdconfig_.runBeforeCmds; i++) + { + sprintf(str, "EXTERNAL:%d", i+1); + _sdconfig_.runBeforeCmd[i].mins = ini_getl(str, "RUNB4STARTTIME", -1, inifile); + ini_gets(str, "COMMAND", NULL, _sdconfig_.runBeforeCmd[i].command, sizearray(_sdconfig_.runBeforeCmd[i].command), inifile); + //if (_sdconfig_.runBeforeCmd[i].mins <= 0 || _sdconfig_.runBeforeCmd[i].mins > 120) { + // logMessage (LOG_ERR, "RUNB4STARTTIME %d is not valid, found in EXTERNAL:%d of configuration file %s \n",_sdconfig_.runBeforeCmd[i].mins, i+1, inifile); + // continue; + //} + logMessage (LOG_DEBUG,"Run external command '%s', %d mins before start time\n",_sdconfig_.runBeforeCmd[i].command, _sdconfig_.runBeforeCmd[i].mins); + } + } + /* idx=0; diff --git a/config.h b/config.h index bd3cb95..2408bd2 100644 --- a/config.h +++ b/config.h @@ -24,6 +24,11 @@ struct CALENDARday { int minute; int *zruntimes; }; + +struct RunB4CalStart { + int mins; + char command[256]; +}; /* struct GPIOextra { char command_high[COMMAND_SIZE]; @@ -96,8 +101,11 @@ struct sprinklerdcfg { long cron_update; int log_level; struct szRunning currentZone; + struct RunB4CalStart *runBeforeCmd; + int runBeforeCmds; char cache_file[512]; - bool eventToUpdateHappened; + //bool eventToUpdateHappened; + uint8_t updateEventMask; int todayRainChance; float todayRainTotal; }; @@ -127,5 +135,26 @@ void read_cache(); #define ON 0 #define OFF 1 */ + +#define UPDATE_RAINTOTAL (1 << 0) // +#define UPDATE_RAINPROBABILITY (1 << 1) // +#define UPDATE_ZONES (1 << 2) // +#define UPDATE_STATUS (1 << 3) // + +#define isEventRainTotal ((_sdconfig_.updateEventMask & UPDATE_RAINTOTAL) == UPDATE_RAINTOTAL) +#define isEventRainProbability ((_sdconfig_.updateEventMask & UPDATE_RAINPROBABILITY) == UPDATE_RAINPROBABILITY) +#define isEventZones ((_sdconfig_.updateEventMask & UPDATE_ZONES) == UPDATE_ZONES) +#define isEventStatus ((_sdconfig_.updateEventMask & UPDATE_STATUS) == UPDATE_STATUS) + +#define setEventRainTotal (_sdconfig_.updateEventMask |= UPDATE_RAINTOTAL) +#define setEventRainProbability (_sdconfig_.updateEventMask |= UPDATE_RAINPROBABILITY) +#define setEventZones (_sdconfig_.updateEventMask |= UPDATE_ZONES) +#define setEventStatus (_sdconfig_.updateEventMask |= UPDATE_STATUS) + +#define clearEventRainTotal (_sdconfig_.updateEventMask &= ~UPDATE_RAINTOTAL) +#define clearEventRainProbability (_sdconfig_.updateEventMask &= ~UPDATE_RAINPROBABILITY) +#define clearEventZones (_sdconfig_.updateEventMask &= ~UPDATE_ZONES) +#define clearEventStatus (_sdconfig_.updateEventMask &= ~UPDATE_STATUS) + #define NO_CHANGE 2 #endif /* CONFIG_H_ */ diff --git a/extras/sprinklerDarkskys.sh b/extras/sprinklerDarkskys.sh deleted file mode 100755 index 4fa78f7..0000000 --- a/extras/sprinklerDarkskys.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# -# darkskyAPI = your DarkSky API key below, it's free to get one -# location = your location lat,long -# probabilityOver = Enable rain delay if probability of rain today is grater than this number. -# Range is 0 to 1, so 0.5 is 50% -# sprinklerdEnableDelay = the URL to SprinklerD -# -# The below are NOT valid, you MUST chaneg them to your information. -darkskyAPI='xxxxxxxxxxxxxxxxxxxxxxxxxxxxx' -location='42.3601,-71.0589' - -probabilityOver=1.0 # 1.0 means don't set delay from this script, use the SprinklerD config (webUI) to decide if to set delay - -sprinklerdEnableDelay="http://localhost/?type=option&option=24hdelay&state=reset" -sprinklerdProbability="http://localhost/?type=sensor&sensor=chanceofrain&value=" - -echoerr() { printf "%s\n" "$*" >&2; } -echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; } - -command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Aborting!"; exit 1; } -command -v jq >/dev/null 2>&1 || { echoerr "jq is not installed. Aborting!"; exit 1; } -command -v bc >/dev/null 2>&1 || { echoerr "bc not installed. Aborting!"; exit 1; } - -darkskyJSON=$(curl -s "https://api.darksky.net/forecast/"$darkskyAPI"/"$location) - -if [ $? -ne 0 ]; then - echoerr "Error reading DarkSkys URL, please check!" - echoerr "Maybe you didn't configure your API and location?" - exit 1; -fi - -probability=$(echo $darkskyJSON | jq '.["daily"].data[0].precipProbability' ) - -#if [ $? -ne 0 ]; then -if [ "$probability" == "null" ]; then - echoerr "Error reading DarkSkys JSON, please check!" - exit 1; -fi - - -echomsg -n "Probability of rain today is "`echo "$probability * 100" | bc`"%" - -curl -s "$sprinklerdProbability`echo \"$probability * 100\" | bc`" > /dev/null - -if (( $(echo "$probability > $probabilityOver" | bc -l) )); then - echomsg -n ", enabeling rain delay" - curl -s "$sprinklerdEnableDelay" > /dev/null -fi - -echomsg "" diff --git a/extras/sprinklerMeteohub.sh b/extras/sprinklerMeteohub.sh deleted file mode 100755 index 1898671..0000000 --- a/extras/sprinklerMeteohub.sh +++ /dev/null @@ -1,30 +0,0 @@ - -# -# Change the URL's below to point to Meteohub server and SprinklerD server -# - -meteohubRainTotal="http://hurricane/meteograph.cgi?text=day1_rain0_total_in" -#meteohubRainTotal="http://192.168.144.101/meteograph.cgi?text=last24h_rain0_total_in" -#meteohubRainTotal="http://192.168.144.101/meteograph.cgi?text=alltime_rain0_total_in" - -sprinklerdSetRainTotal="http://localhost/?type=sensor&sensor=raintotal&value=" - -echoerr() { printf "%s\n" "$*" >&2; } -echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; } - -command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Aborting!"; exit 1; } - -rainTotal=$(curl -s "$meteohubRainTotal") - -if [ $? -ne 0 ]; then - echoerr "Error reading Metrohub URL, please check!" - echoerr "Assuming rain sensor is not responding, not sending rain total to SprinklerD" - exit 1; -fi - -echomsg -n "Rain total is $rainTotal\" at `date`" - - -curl -s "$sprinklerdSetRainTotal$rainTotal" > /dev/null - -echomsg "" diff --git a/extras/sprinklerOpenWeather.sh b/extras/sprinklerOpenWeather.sh deleted file mode 100755 index c6a1063..0000000 --- a/extras/sprinklerOpenWeather.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# -# openweatherAPI = your Openweather API key below, it's free to get one -# lat = your latitude -# lon = your longitude -# probabilityOver = Enable rain delay if probability of rain today is grater than this number. -# Range is 0 to 1, so 0.5 is 50% -# sprinklerdEnableDelay = the URL to SprinklerD -# -# The below are NOT valid, you MUST chaneg them to your information. -openweatherAPI='xxxxxxxxxxxxxxxxxxxxxxxxxx' -lat='42.3601' -lon='-71.0589' - -probabilityOver=1.0 # 1.0 means don't set delay from this script, use the SprinklerD config (webUI) to decide if to set delay - -sprinklerdEnableDelay="http://localhost/?type=option&option=24hdelay&state=reset" -sprinklerdProbability="http://localhost/?type=sensor&sensor=chanceofrain&value=" - -echoerr() { printf "%s\n" "$*" >&2; } -echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; } - -command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Aborting!"; exit 1; } -command -v jq >/dev/null 2>&1 || { echoerr "jq is not installed. Aborting!"; exit 1; } -command -v bc >/dev/null 2>&1 || { echoerr "bc not installed. Aborting!"; exit 1; } - -openweatherJSON=$(curl -s "https://api.openweathermap.org/data/2.5/onecall?lat="$lat"&lon="$lon"&appid="$openweatherAPI"&exclude=current,minutely,hourly,alerts") - -if [ $? -ne 0 ]; then - echoerr "Error reading OpenWeather URL, please check!" - echoerr "Maybe you didn't configure your API and location?" - exit 1; -fi - -probability=$(echo $openweatherJSON | jq '.["daily"][0].pop' ) - -#if [ $? -ne 0 ]; then -if [ "$probability" == "null" ]; then - echoerr "Error reading OpenWeather JSON, please check!" - exit 1; -fi - - -echomsg -n "Probability of rain today is "`echo "$probability * 100" | bc`"%" - -curl -s "$sprinklerdProbability`echo \"$probability * 100\" | bc`" > /dev/null - -if (( $(echo "$probability > $probabilityOver" | bc -l) )); then - echomsg -n ", enabeling rain delay" - curl -s "$sprinklerdEnableDelay" > /dev/null -fi - -echomsg "" diff --git a/extras/sprinklerRainProbability.sh b/extras/sprinklerRainProbability.sh index 11bbe7e..dd43672 100755 --- a/extras/sprinklerRainProbability.sh +++ b/extras/sprinklerRainProbability.sh @@ -10,6 +10,9 @@ # # The below are NOT valid, you MUST change them to your information. +#Can use below to track best api for location +#https://www.forecastadvisor.com/ + # API Keys from the following are supported # openweathermap.org weatherapi.com weatherbit.io climacell.co @@ -17,9 +20,12 @@ openweatherAPIkey='-----' weatherbitAPIkey='-----' climacellAPIkey='-----' weatherAPIkey='-----' +accuweatherAPI='-----' lat='42.3601' lon='-71.0589' +zip='11111' + # 101 means don't set rain delay from this script, use the SprinklerD config (webUI) to decide if to set delay probabilityOver=101 @@ -43,13 +49,27 @@ openweatherURL="https://api.openweathermap.org/data/2.5/onecall?lat=$lat&lon=$lo weatherbitURL="https://api.weatherbit.io/v2.0/forecast/daily?key=$weatherbitAPIkey&lat=$lat&lon=$lon" climacellURL="https://data.climacell.co/v4/timelines?location=$lat%2C$lon&fields=precipitationProbability×teps=1d&units=metric&apikey=$climacellAPIkey" weatherAPIlURL="http://api.weatherapi.com/v1/forecast.json?key=$weatherAPIkey&q=$lat,$lon&days=1&aqi=no&alerts=no" +openmeteoURL="https://api.open-meteo.com/v1/forecast?latitude=$lat&longitude=$lon&daily=precipitation_probability_max&timezone=America%2FChicago&forecast_days=1" +accuweatherURL="http://dataservice.accuweather.com/forecasts/v1/daily/1day/$zip?apikey=$accuweatherAPI&details=true" +pirateweatherURL="https://api.pirateweather.net/forecast/$pirateweatherAPI/$lat%2C$lon?exclude=minutely%2Chourly%2Calerts" + true=0 false=1 +OUT_FIRST=1 +OUT_MAX=2 +OUT_MIN=3 +OUT_AVERAGE=4 +OUT_PRINT_ONLY=5 + +output=$OUT_FIRST + echoerr() { printf "%s\n" "$*" >&2; } echomsg() { if [ -t 1 ]; then echo "$@" 1>&2; fi; } + + function getURL() { url=$1 jq=$2 @@ -66,6 +86,11 @@ function getURL() { probability=$(echo $JSON | jq "$jq" ) fi + if ! [[ $probability =~ ^[0-9]+([.][0-9]+)?$ ]]; then + echoerr "Did not get a number from probability API result=$probability" + return $false + fi + probability=$(echo "$probability * $factor / 1" | bc ) echo $probability return $true @@ -76,63 +101,129 @@ command -v curl >/dev/null 2>&1 || { echoerr "curl is not installed. Aborting!" command -v jq >/dev/null 2>&1 || { echoerr "jq is not installed. Aborting!"; exit 1; } command -v bc >/dev/null 2>&1 || { echoerr "bc not installed. Aborting!"; exit 1; } -pop=-1 +probArray=() if [ "$#" -lt 1 ]; then - echomsg "Pass at least one command line parameter (openweather, weatherbit, climacell, weatherapi)" + echomsg "Pass at least one command line parameter (openweather, weatherbit, climacell, weatherapi, openmeteo, accuweather, pirateweather)" + echomsg "if you pass more than 1 of above, can also use one of folowing -max -min -average -first" + echomsg "Example:-" + echomsg " $0 openweather weatherapi -average" + echomsg "" exit 1; fi for var in "$@" do case "$var" in + -max) + output=$OUT_MAX + ;; + -min) + output=$OUT_MIN + ;; + -average) + output=$OUT_AVERAGE + ;; + -first) + output=$OUT_FIRST + ;; + -print) + output=$OUT_PRINT_ONLY + ;; openweather) if [ "$openweatherAPIkey" == "-----" ]; then echomsg "missing OpenWeather API key" && continue; fi if probability=$(getURL "$openweatherURL" '.["daily"][0].pop' "100"); then echomsg "OpenWeather probability of rain today is $probability%" - pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + #pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + probArray+=($probability) fi ;; weatherbit) if [ "$weatherbitAPIkey" == "-----" ]; then echomsg "missing WeatherBit API key" && continue; fi if probability=$(getURL "$weatherbitURL" '.data[0].pop' "1"); then echomsg "WeatherBit probability of rain today is $probability%" - pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + #pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + probArray+=($probability) fi ;; climacell) if [ "$climacellAPIkey" == "-----" ]; then echomsg "missing ClimaCell API key" && continue; fi if probability=$(getURL "$climacellURL" '.data.timelines[0].intervals[0].values.precipitationProbability' "1"); then echomsg "ClimaCell probability of rain today is $probability%" - pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + probArray+=($probability) fi ;; weatherapi) if [ "$weatherAPIkey" == "-----" ]; then echomsg "missing WeatherAPI API key" && continue; fi if probability=$(getURL "$weatherAPIlURL" '.forecast.forecastday[0].day.daily_chance_of_rain' "1"); then echomsg "WeatherAPI probability of rain today is $probability%" - pop=$(echo "if($probability>$pop)print $probability else print $pop" | bc -l) + probArray+=($probability) + fi + ;; + openmeteo) + if probability=$(getURL "$openmeteoURL" '.daily.precipitation_probability_max[]' "1"); then + echomsg "Open Meteo probability of rain today is $probability%" + probArray+=($probability) + fi + ;; + accuweather) + if probability=$(getURL "$accuweatherURL" '.DailyForecasts[0].Day.PrecipitationProbability' "1"); then + echomsg "AccuWeather probability of rain today is $probability%" + probArray+=($probability) + fi + ;; + pirateweather) + if probability=$(getURL "$pirateweatherURL" '.daily.data[0].precipProbability' "100"); then + echomsg "PirateWeather probability of rain today is $probability%" + probArray+=($probability) fi ;; esac done -# Remove all new line, carriage return, tab characters -# from the string, to allow integer comparison -pop="${pop//[$'\t\r\n ']}" -if [ "$pop" -lt 0 ]; then - echoerr "Error reading Probability, please check!" +if [ ${#probArray[@]} -le 0 ]; then + echoerr "No rain probability values found" exit 1 fi -echomsg "Chance of rain $pop%" -curl -s "$sprinklerdProbability$pop" > /dev/null - -if (( $(echo "$pop > $probabilityOver" | bc -l) )); then +prop=${probArray[0]} + +case $output in + $OUT_PRINT_ONLY) + exit 0; + ;; + $OUT_FIRST) + prop=${probArray[0]} + ;; + $OUT_MAX) + for n in "${probArray[@]}" ; do + ((n > prop)) && prop=$n + done + ;; + $OUT_MIN) + for n in "${probArray[@]}" ; do + ((n < prop)) && prop=$n + done + ;; + $OUT_AVERAGE) + prop=0 + for n in "${probArray[@]}" ; do + let prop=prop+n + done + let prop=prop/${#probArray[@]} + ;; +esac + +echomsg "Chance of rain $prop%" + +curl -s "$sprinklerdProbability$prop" > /dev/null + +if (( $(echo "$prop > $probabilityOver" | bc -l) )); then echomsg "Enabeling rain delay" curl -s "$sprinklerdEnableDelay" > /dev/null fi exit 0 + diff --git a/hassio.c b/hassio.c index 644d5e8..a24860f 100644 --- a/hassio.c +++ b/hassio.c @@ -10,13 +10,18 @@ #include "net_services.h" #include "version.h" + + +//#define HASS_EXPIRE ""\"expire_after"\": 300," +#define HASS_EXPIRE "" + #define HASS_DEVICE "\"identifiers\": " \ "[\"SprinklerD\"]," \ " \"sw_version\": \"" SD_VERSION "\"," \ " \"model\": \"Sprinkler Daemon\"," \ " \"name\": \"SprinklerD\"," \ " \"manufacturer\": \"SprinklerD\"," \ - " \"suggested_area\": \"pool\"" + " \"suggested_area\": \"garden\"" #define HASS_AVAILABILITY "\"payload_available\" : \"1\"," \ "\"payload_not_available\" : \"0\"," \ @@ -32,6 +37,18 @@ const char *HASSIO_TEXT_SENSOR_DISCOVER = "{" "\"icon\": \"mdi:card-text\"" "}"; +const char *HASSIO_SENSOR_DISCOVER = "{" + "\"device\": {" HASS_DEVICE "}," + "\"availability\": {" HASS_AVAILABILITY "}," + "\"type\": \"sensor\"," + "\"unique_id\": \"%s\"," + "\"name\": \"%s\"," + "\"state_topic\": \"%s/%s\"," + "\"value_template\": \"{{ value_json }}\"," + "\"unit_of_measurement\": \"%s\"," + "\"icon\": \"%s\"" +"}"; + const char *HASSIO_SWITCH_DISCOVER = "{" "\"device\": {" HASS_DEVICE "}," "\"availability\": {" HASS_AVAILABILITY "}," @@ -41,9 +58,7 @@ const char *HASSIO_SWITCH_DISCOVER = "{" "\"state_topic\": \"%s/%s\"," "\"command_topic\": \"%s/%s/set\"," "\"payload_on\": \"1\"," - "\"payload_off\": \"0\"," - "\"qos\": 1," - "\"retain\": false" + "\"payload_off\": \"0\"" "}"; const char *HASSIO_VALVE_DISCOVER = "{" @@ -57,9 +72,7 @@ const char *HASSIO_VALVE_DISCOVER = "{" "\"command_topic\": \"%s/zone%d/set\"," // 1 "\"value_template\": \"{%% set values = { '0':'closed', '1':'open'} %%}{{ values[value] if value in values.keys() else 'closed' }}\"," "\"payload_open\": \"1\"," - "\"payload_close\": \"0\"," - "\"qos\": 1," - "\"retain\": false" + "\"payload_close\": \"0\"" "}"; void publish_mqtt_hassio_discover(struct mg_connection *nc) @@ -121,6 +134,28 @@ void publish_mqtt_hassio_discover(struct mg_connection *nc) send_mqtt_msg(nc, topic, msg); + sprintf(id,"sprinklerd_rainprobability"); + sprintf(topic, "%s/sensor/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id); + sprintf(msg, HASSIO_SENSOR_DISCOVER, + _sdconfig_.mqtt_topic, + id, + "Todays Rain Probability", + _sdconfig_.mqtt_topic, "chanceofrain", + "%", + "mdi:water-percent" ); + send_mqtt_msg(nc, topic, msg); + + sprintf(id,"sprinklerd_raintotal"); + sprintf(topic, "%s/sensor/sprinklerd/%s/config", _sdconfig_.mqtt_ha_dis_topic, id); + sprintf(msg, HASSIO_SENSOR_DISCOVER, + _sdconfig_.mqtt_topic, + id, + "Todays Rain Total", + _sdconfig_.mqtt_topic, "raintotal", + "\\\"", + "mdi:weather-hail" ); + send_mqtt_msg(nc, topic, msg); + //for (i=(_sdconfig_.master_valve?0:1); i <= _sdconfig_.zones ; i++) diff --git a/mongoose.c b/mongoose.c index 141e501..fde66de 100644 --- a/mongoose.c +++ b/mongoose.c @@ -1,11 +1,26 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka + * Copyright (c) 2013-2020 Cesanta Software Limited + * All rights reserved + * + * This software is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see . + * + * You are free to use this software under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this software under a commercial + * license, as set out in . + */ + #include "mongoose.h" #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_internal.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_INTERNAL_H_ #define CS_MONGOOSE_SRC_INTERNAL_H_ @@ -38,7 +53,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, @@ -86,6 +103,9 @@ extern void *(*test_calloc)(size_t count, size_t size); #if MG_ENABLE_HTTP struct mg_serve_http_opts; +MG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data( + struct mg_connection *c); + /* * Reassemble the content of the buffer (buf, blen) which should be * in the HTTP chunked encoding, by collapsing data chunks to the @@ -162,10 +182,6 @@ MG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len, #ifdef MG_MODULE_LINES #line 1 "common/mg_mem.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_MG_MEM_H_ #define CS_COMMON_MG_MEM_H_ @@ -198,10 +214,6 @@ extern "C" { #ifdef MG_MODULE_LINES #line 1 "common/cs_base64.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef EXCLUDE_COMMON @@ -406,10 +418,6 @@ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len) { #ifdef MG_MODULE_LINES #line 1 "common/cs_dbg.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_CS_DBG_H_ #define CS_COMMON_CS_DBG_H_ @@ -424,6 +432,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 @@ -454,51 +466,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. - * - * For the pattern syntax, refer to `mg_match_prefix()` in `str_util.h`. - * - * Example: - * ```c - * void foo(void) { - * LOG(LL_INFO, ("hello from foo")); - * } + * 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. * - * void bar(void) { - * LOG(LL_INFO, ("hello from bar")); - * } + * 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 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 @@ -511,11 +500,9 @@ void cs_log_set_file(FILE *file); * Prints log to the current log file, appends "\n" in the end and flushes the * stream. */ -void cs_log_printf(const char *fmt, ...) -#ifdef __GNUC__ - __attribute__((format(printf, 1, 2))) -#endif - ; +void cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2); + +#if CS_ENABLE_STDIO /* * Format and print message `x` with the given level `l`. Example: @@ -525,11 +512,19 @@ void cs_log_printf(const char *fmt, ...) * 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 + +#define LOG(l, x) ((void) l) + +#endif + #ifndef CS_NDEBUG /* @@ -558,10 +553,6 @@ void cs_log_printf(const char *fmt, ...) #ifdef MG_MODULE_LINES #line 1 "common/cs_dbg.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "common/cs_dbg.h" */ @@ -572,19 +563,17 @@ void cs_log_printf(const char *fmt, ...) /* 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 LL_ERROR; #endif -static char *s_filter_pattern = NULL; -static size_t s_filter_pattern_len; - -void cs_log_set_filter(const char *pattern) WEAK; - #if CS_ENABLE_STDIO +static char *s_file_level = NULL; + +void cs_log_set_file_level(const char *file_level) WEAK; FILE *cs_log_file WEAK = NULL; @@ -594,34 +583,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(); @@ -650,15 +667,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 @@ -666,10 +683,6 @@ void cs_log_set_level(enum cs_log_level level) { #ifdef MG_MODULE_LINES #line 1 "common/cs_dirent.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_CS_DIRENT_H_ #define CS_COMMON_CS_DIRENT_H_ @@ -708,10 +721,6 @@ struct dirent *readdir(DIR *dir); #ifdef MG_MODULE_LINES #line 1 "common/cs_dirent.c" #endif -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - */ #ifndef EXCLUDE_COMMON @@ -807,10 +816,6 @@ typedef int cs_dirent_dummy; #ifdef MG_MODULE_LINES #line 1 "common/cs_time.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "common/cs_time.h" */ @@ -893,10 +898,6 @@ double cs_timegm(const struct tm *tm) { #ifdef MG_MODULE_LINES #line 1 "common/cs_endian.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_CS_ENDIAN_H_ #define CS_COMMON_CS_ENDIAN_H_ @@ -1398,10 +1399,6 @@ void cs_hmac_sha1(const unsigned char *key, size_t keylen, #ifdef MG_MODULE_LINES #line 1 "common/mbuf.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef EXCLUDE_COMMON @@ -1470,10 +1467,21 @@ size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) { } a->len += len; } else { - size_t new_size = (size_t)((a->len + len) * MBUF_SIZE_MULTIPLIER); - if ((p = (char *) MBUF_REALLOC(a->buf, new_size)) != NULL) { + size_t min_size = (a->len + len); + size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER); + if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) { + new_size = min_size + MBUF_SIZE_MAX_HEADROOM; + } + p = (char *) MBUF_REALLOC(a->buf, new_size); + if (p == NULL && new_size != min_size) { + new_size = min_size; + p = (char *) MBUF_REALLOC(a->buf, new_size); + } + if (p != NULL) { a->buf = p; - memmove(a->buf + off + len, a->buf + off, a->len - off); + if (off != a->len) { + memmove(a->buf + off + len, a->buf + off, a->len - off); + } if (buf != NULL) memcpy(a->buf + off, buf, len); a->len += len; a->size = new_size; @@ -1490,6 +1498,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) { @@ -1498,18 +1522,27 @@ 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" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "common/mg_mem.h" */ /* Amalgamated: #include "common/mg_str.h" */ +/* Amalgamated: #include "common/platform.h" */ +#include #include #include @@ -1586,8 +1619,10 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK; int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { size_t i = 0; while (i < str1.len && i < str2.len) { - if (str1.p[i] < str2.p[i]) return -1; - if (str1.p[i] > str2.p[i]) return 1; + int c1 = str1.p[i]; + int c2 = str2.p[i]; + if (c1 < c2) return -1; + if (c1 > c2) return 1; i++; } if (i < str1.len) return 1; @@ -1609,6 +1644,29 @@ int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) { return mg_strcmp(s1, s2); } +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) WEAK; +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while (i < str1.len && i < str2.len) { + int c1 = tolower((int) str1.p[i]); + int c2 = tolower((int) str2.p[i]); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + i++; + } + if (i < str1.len) return 1; + if (i < str2.len) return -1; + return 0; +} + +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, @@ -1622,13 +1680,28 @@ const char *mg_strstr(const struct mg_str haystack, } return NULL; } + +struct mg_str mg_strstrip(struct mg_str s) WEAK; +struct mg_str mg_strstrip(struct mg_str s) { + while (s.len > 0 && isspace((int) *s.p)) { + s.p++; + s.len--; + } + while (s.len > 0 && isspace((int) *(s.p + s.len - 1))) { + s.len--; + } + 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 -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - */ #ifndef EXCLUDE_COMMON @@ -2148,23 +2221,6 @@ size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - * - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* Amalgamated: #include "common/cs_time.h" */ /* Amalgamated: #include "mg_dns.h" */ @@ -2174,6 +2230,13 @@ size_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) { #define MG_MAX_HOST_LEN 200 +#ifndef MG_TCP_IO_SIZE +#define MG_TCP_IO_SIZE 1460 +#endif +#ifndef MG_UDP_IO_SIZE +#define MG_UDP_IO_SIZE 1460 +#endif + #define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \ memcpy(dst, src, sizeof(*dst)); @@ -2214,8 +2277,6 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *conn) { MG_INTERNAL void mg_call(struct mg_connection *nc, mg_event_handler_t ev_handler, void *user_data, int ev, void *ev_data) { - static int nesting_level = 0; - nesting_level++; if (ev_handler == NULL) { /* * If protocol handler is specified, call it. Otherwise, call user-specified @@ -2224,7 +2285,7 @@ MG_INTERNAL void mg_call(struct mg_connection *nc, ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler; } if (ev != MG_EV_POLL) { - DBG(("%p %s ev=%d ev_data=%p flags=%lu rmbl=%d smbl=%d", nc, + DBG(("%p %s ev=%d ev_data=%p flags=0x%lx rmbl=%d smbl=%d", nc, ev_handler == nc->handler ? "user" : "proto", ev, ev_data, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); } @@ -2237,33 +2298,25 @@ MG_INTERNAL void mg_call(struct mg_connection *nc, #endif if (ev_handler != NULL) { unsigned long flags_before = nc->flags; - size_t recv_mbuf_before = nc->recv_mbuf.len, recved; ev_handler(nc, ev, ev_data MG_UD_ARG(user_data)); - recved = (recv_mbuf_before - nc->recv_mbuf.len); /* Prevent user handler from fiddling with system flags. */ if (ev_handler == nc->handler && nc->flags != flags_before) { nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) | (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK); } - /* It's important to not double-count recved bytes, and since mg_call can be - * called recursively (e.g. proto_handler invokes user handler), we keep - * track of recursion and only report received bytes at the top level. */ - if (nesting_level == 1 && recved > 0 && !(nc->flags & MG_F_UDP)) { - nc->iface->vtable->recved(nc, recved); - } } + if (ev != MG_EV_POLL) nc->mgr->num_calls++; if (ev != MG_EV_POLL) { - DBG(("%p after %s flags=%lu rmbl=%d smbl=%d", nc, + DBG(("%p after %s flags=0x%lx rmbl=%d smbl=%d", nc, ev_handler == nc->handler ? "user" : "proto", nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); } - nesting_level--; #if !MG_ENABLE_CALLBACK_USERDATA (void) user_data; #endif } -void mg_if_timer(struct mg_connection *c, double now) { +MG_INTERNAL void mg_timer(struct mg_connection *c, double now) { if (c->ev_timer_time > 0 && now >= c->ev_timer_time) { double old_value = c->ev_timer_time; c->ev_timer_time = 0; @@ -2271,13 +2324,53 @@ void mg_if_timer(struct mg_connection *c, double now) { } } -void mg_if_poll(struct mg_connection *nc, time_t now) { - if (!(nc->flags & MG_F_SSL) || (nc->flags & MG_F_SSL_HANDSHAKE_DONE)) { - mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now); +MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) { + size_t avail; + if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0; + avail = conn->recv_mbuf_limit - conn->recv_mbuf.len; + return avail > max ? max : avail; +} + +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) { + 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; + } +#if MG_ENABLE_SSL + if ((nc->flags & (MG_F_SSL | MG_F_LISTENING | MG_F_CONNECTING)) == MG_F_SSL) { + /* SSL library may have data to be delivered to the app in its buffers, + * drain them. */ + int recved = 0; + do { + if (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)) break; + if (recv_avail_size(nc, MG_TCP_IO_SIZE) <= 0) break; + recved = mg_do_recv(nc); + } while (recved > 0); } +#endif /* MG_ENABLE_SSL */ + mg_timer(nc, now); + { + time_t now_t = (time_t) now; + mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now_t); + } + return 1; } void mg_destroy_conn(struct mg_connection *conn, int destroy_if) { + if (conn->sock != INVALID_SOCKET) { /* Don't print timer-only conns */ + LOG(LL_DEBUG, ("%p 0x%lx %d", conn, conn->flags, destroy_if)); + } if (destroy_if) conn->iface->vtable->destroy_conn(conn); if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) { conn->proto_data_destructor(conn->proto_data); @@ -2293,12 +2386,23 @@ void mg_destroy_conn(struct mg_connection *conn, int destroy_if) { } void mg_close_conn(struct mg_connection *conn) { - DBG(("%p %lu %d", conn, conn->flags, conn->sock)); + /* 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); } #endif + /* + * Clearly mark the connection as going away (if not already). + * Some net_if impls (LwIP) need this for cleanly handling half-dead conns. + */ + conn->flags |= MG_F_CLOSE_IMMEDIATELY; mg_remove_conn(conn); conn->iface->vtable->destroy_conn(conn); mg_call(conn, NULL, conn->user_data, MG_EV_CLOSE, NULL); @@ -2330,15 +2434,6 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data, signal(SIGPIPE, SIG_IGN); #endif -#if MG_ENABLE_SSL - { - static int init_done; - if (!init_done) { - mg_ssl_if_init(); - init_done++; - } - } -#endif { int i; if (opts.num_ifaces == 0) { @@ -2351,7 +2446,7 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data, m->num_ifaces = opts.num_ifaces; m->ifaces = (struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces); - for (i = 0; i < mg_num_ifaces; i++) { + for (i = 0; i < opts.num_ifaces; i++) { m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m); m->ifaces[i]->vtable->init(m->ifaces[i]); } @@ -2361,6 +2456,15 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data, } DBG(("==================================")); DBG(("init mgr=%p", m)); +#if MG_ENABLE_SSL + { + static int init_done; + if (!init_done) { + mg_ssl_if_init(); + init_done++; + } + } +#endif } void mg_mgr_free(struct mg_mgr *m) { @@ -2379,6 +2483,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); } @@ -2394,19 +2499,14 @@ void mg_mgr_free(struct mg_mgr *m) { MG_FREE((char *) m->nameserver); } -time_t mg_mgr_poll(struct mg_mgr *m, int timeout_ms) { - int i; - time_t now = 0; /* oh GCC, seriously ? */ - - if (m->num_ifaces == 0) { - LOG(LL_ERROR, ("cannot poll: no interfaces")); - return 0; - } +int mg_mgr_poll(struct mg_mgr *m, int timeout_ms) { + int i, num_calls_before = m->num_calls; for (i = 0; i < m->num_ifaces; i++) { - now = m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms); + m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms); } - return now; + + return (m->num_calls - num_calls_before); } int mg_vprintf(struct mg_connection *nc, const char *fmt, va_list ap) { @@ -2447,8 +2547,8 @@ static int mg_resolve2(const char *host, struct in_addr *ina) { return 0; } for (p = servinfo; p != NULL; p = p->ai_next) { - memcpy(&h, &p->ai_addr, sizeof(struct sockaddr_in *)); - memcpy(ina, &h->sin_addr, sizeof(ina)); + memcpy(&h, &p->ai_addr, sizeof(h)); + memcpy(ina, &h->sin_addr, sizeof(*ina)); } freeaddrinfo(servinfo); return 1; @@ -2603,6 +2703,36 @@ MG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa, return port < 0xffffUL && (ch == '\0' || ch == ',' || isspace(ch)) ? len : -1; } +#if MG_ENABLE_SSL +MG_INTERNAL void mg_ssl_handshake(struct mg_connection *nc) { + int err = 0; + int server_side = (nc->listener != NULL); + enum mg_ssl_if_result res; + if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) return; + res = mg_ssl_if_handshake(nc); + + if (res == MG_SSL_OK) { + nc->flags |= MG_F_SSL_HANDSHAKE_DONE; + nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE); + if (server_side) { + mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa); + } else { + mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err); + } + } else if (res == MG_SSL_WANT_READ) { + nc->flags |= MG_F_WANT_READ; + } else if (res == MG_SSL_WANT_WRITE) { + nc->flags |= MG_F_WANT_WRITE; + } else { + if (!server_side) { + err = res; + mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err); + } + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } +} +#endif /* MG_ENABLE_SSL */ + struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) { struct mg_add_sock_opts opts; struct mg_connection *nc; @@ -2616,95 +2746,132 @@ struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) { nc->iface = lc->iface; if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL; mg_add_conn(nc->mgr, nc); - DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags)); + LOG(LL_DEBUG, ("%p %p %d %#x", lc, nc, (int) nc->sock, (int) nc->flags)); return nc; } void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa, size_t sa_len) { - (void) sa_len; + LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"), + inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port))); nc->sa = *sa; - mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa); +#if MG_ENABLE_SSL + if (nc->listener->flags & MG_F_SSL) { + nc->flags |= MG_F_SSL; + if (mg_ssl_if_conn_accept(nc, nc->listener) == MG_SSL_OK) { + mg_ssl_handshake(nc); + } else { + mg_close_conn(nc); + } + } else +#endif + { + mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa); + } + (void) sa_len; } void mg_send(struct mg_connection *nc, const void *buf, int len) { nc->last_io_time = (time_t) mg_time(); - if (nc->flags & MG_F_UDP) { - nc->iface->vtable->udp_send(nc, buf, len); - } else { - nc->iface->vtable->tcp_send(nc, buf, len); - } -} - -void mg_if_sent_cb(struct mg_connection *nc, int num_sent) { - DBG(("%p %d", nc, num_sent)); -#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP - if (nc->mgr && nc->mgr->hexdump_file != NULL) { - char *buf = nc->send_mbuf.buf; - mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, num_sent, MG_EV_SEND); - } -#endif - if (num_sent < 0) { - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } else { - mbuf_remove(&nc->send_mbuf, num_sent); - mbuf_trim(&nc->send_mbuf); - } - mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &num_sent); + mbuf_append(&nc->send_mbuf, buf, len); } -MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len, - int own) { - DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len)); +static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len); +static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len); -#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP - if (nc->mgr && nc->mgr->hexdump_file != NULL) { - mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, len, MG_EV_RECV); +static int mg_do_recv(struct mg_connection *nc) { + int res = 0; + char *buf = NULL; + size_t len = (nc->flags & MG_F_UDP ? MG_UDP_IO_SIZE : MG_TCP_IO_SIZE); + if ((nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) || + ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) { + return -1; } -#endif - - if (nc->flags & MG_F_CLOSE_IMMEDIATELY) { - DBG(("%p discarded %d bytes", nc, len)); - /* - * This connection will not survive next poll. Do not deliver events, - * send data to /dev/null without acking. - */ - if (own) { - MG_FREE(buf); + do { + len = recv_avail_size(nc, len); + if (len == 0) { + res = -2; + break; } - return; - } - nc->last_io_time = (time_t) mg_time(); - if (!own) { - mbuf_append(&nc->recv_mbuf, buf, len); - } else if (nc->recv_mbuf.len == 0) { - /* Adopt buf as recv_mbuf's backing store. */ - mbuf_free(&nc->recv_mbuf); - nc->recv_mbuf.buf = (char *) buf; - nc->recv_mbuf.size = nc->recv_mbuf.len = len; - } else { - mbuf_append(&nc->recv_mbuf, buf, len); - MG_FREE(buf); - } - mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &len); + 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; } -void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) { - mg_recv_common(nc, buf, len, own); +void mg_if_can_recv_cb(struct mg_connection *nc) { + mg_do_recv(nc); } -void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, - union socket_address *sa, size_t sa_len) { - assert(nc->flags & MG_F_UDP); - DBG(("%p %u", nc, (unsigned int) len)); - if (nc->flags & MG_F_LISTENING) { - struct mg_connection *lc = nc; - /* - * Do we have an existing connection for this source? - * This is very inefficient for long connection lists. +static int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len) { + int n = 0; +#if MG_ENABLE_SSL + if (nc->flags & MG_F_SSL) { + if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { + n = mg_ssl_if_read(nc, buf, len); + DBG(("%p <- %d bytes (SSL)", nc, n)); + if (n < 0) { + if (n == MG_SSL_WANT_READ) { + nc->flags |= MG_F_WANT_READ; + n = 0; + } else { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } + } else if (n > 0) { + nc->flags &= ~MG_F_WANT_READ; + } + } else { + mg_ssl_handshake(nc); + } + } else +#endif + { + n = nc->iface->vtable->tcp_recv(nc, buf, len); + DBG(("%p <- %d bytes", nc, n)); + } + if (n > 0) { + nc->recv_mbuf.len += n; + nc->last_io_time = (time_t) mg_time(); +#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP + if (nc->mgr && nc->mgr->hexdump_file != NULL) { + mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV); + } +#endif + mbuf_trim(&nc->recv_mbuf); + mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + } else if (n < 0) { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } + mbuf_trim(&nc->recv_mbuf); + return n; +} + +static int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) { + int n = 0; + struct mg_connection *lc = nc; + union socket_address sa; + size_t sa_len = sizeof(sa); + n = nc->iface->vtable->udp_recv(lc, buf, len, &sa, &sa_len); + if (n < 0) { + lc->flags |= MG_F_CLOSE_IMMEDIATELY; + goto out; + } + if (nc->flags & MG_F_LISTENING) { + /* + * Do we have an existing connection for this source? + * This is very inefficient for long connection lists. */ + lc = nc; for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) { - if (memcmp(&nc->sa.sa, &sa->sa, sa_len) == 0 && nc->listener == lc) { + if (memcmp(&nc->sa.sa, &sa.sa, sa_len) == 0 && nc->listener == lc) { break; } } @@ -2716,7 +2883,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, if (nc != NULL) { nc->sock = lc->sock; nc->listener = lc; - nc->sa = *sa; + nc->sa = sa; nc->proto_handler = lc->proto_handler; nc->user_data = lc->user_data; nc->recv_mbuf_limit = lc->recv_mbuf_limit; @@ -2735,18 +2902,90 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, nc->flags |= MG_F_SEND_AND_CLOSE; mg_add_conn(lc->mgr, nc); mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa); - } else { - DBG(("OOM")); - /* No return here, we still need to drop on the floor */ } } } if (nc != NULL) { - mg_recv_common(nc, buf, len, 1); - } else { - /* Drop on the floor. */ - MG_FREE(buf); + DBG(("%p <- %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr), + ntohs(nc->sa.sin.sin_port))); + if (nc == lc) { + nc->recv_mbuf.len += n; + } else { + mbuf_append(&nc->recv_mbuf, buf, n); + } + mbuf_trim(&lc->recv_mbuf); + lc->last_io_time = nc->last_io_time = (time_t) mg_time(); +#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP + if (nc->mgr && nc->mgr->hexdump_file != NULL) { + mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV); + } +#endif + if (n != 0) { + mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n); + } + } + +out: + mbuf_free(&lc->recv_mbuf); + return n; +} + +void mg_if_can_send_cb(struct mg_connection *nc) { + int n = 0; + const char *buf = nc->send_mbuf.buf; + size_t len = nc->send_mbuf.len; + + if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) { + return; + } + if (!(nc->flags & MG_F_UDP)) { + if (nc->flags & MG_F_LISTENING) return; + if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE; + } +#if MG_ENABLE_SSL + if (nc->flags & MG_F_SSL) { + if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { + if (len > 0) { + n = mg_ssl_if_write(nc, buf, len); + DBG(("%p -> %d bytes (SSL)", nc, n)); + } + if (n < 0) { + if (n == MG_SSL_WANT_WRITE) { + nc->flags |= MG_F_WANT_WRITE; + n = 0; + } else { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } + } else { + nc->flags &= ~MG_F_WANT_WRITE; + } + } else { + mg_ssl_handshake(nc); + } + } else +#endif + if (len > 0) { + if (nc->flags & MG_F_UDP) { + n = nc->iface->vtable->udp_send(nc, buf, len); + } else { + n = nc->iface->vtable->tcp_send(nc, buf, len); + } + DBG(("%p -> %d bytes", nc, n)); + } + +#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP + if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) { + mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND); } +#endif + if (n < 0) { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } else if (n > 0) { + nc->last_io_time = (time_t) mg_time(); + mbuf_remove(&nc->send_mbuf, n); + mbuf_trim(&nc->send_mbuf); + } + if (n != 0) mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &n); } /* @@ -2758,8 +2997,8 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc, int proto, union socket_address *sa) { - DBG(("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp", - inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port))); + LOG(LL_DEBUG, ("%p %s://%s:%hu", nc, proto == SOCK_DGRAM ? "udp" : "tcp", + inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port))); nc->flags |= MG_F_CONNECTING; if (proto == SOCK_DGRAM) { @@ -2772,12 +3011,21 @@ MG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc, } void mg_if_connect_cb(struct mg_connection *nc, int err) { - DBG(("%p connect, err=%d", nc, err)); + LOG(LL_DEBUG, + ("%p %s://%s:%hu -> %d", nc, (nc->flags & MG_F_UDP ? "udp" : "tcp"), + inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port), err)); nc->flags &= ~MG_F_CONNECTING; if (err != 0) { nc->flags |= MG_F_CLOSE_IMMEDIATELY; } - mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err); +#if MG_ENABLE_SSL + if (err == 0 && (nc->flags & MG_F_SSL)) { + mg_ssl_handshake(nc); + } else +#endif + { + mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err); + } } #if MG_ENABLE_ASYNC_RESOLVER @@ -2835,6 +3083,16 @@ struct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address, return mg_connect_opt(mgr, address, MG_CB(callback, user_data), opts); } +void mg_ev_handler_empty(struct mg_connection *c, int ev, + void *ev_data MG_UD_ARG(void *user_data)) { + (void) c; + (void) ev; + (void) ev_data; +#if MG_ENABLE_CALLBACK_USERDATA + (void) user_data; +#endif +} + struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address, MG_CB(mg_event_handler_t callback, void *user_data), @@ -2846,6 +3104,8 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address, MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts); + if (callback == NULL) callback = mg_ev_handler_empty; + if ((nc = mg_create_connection(mgr, callback, add_sock_opts)) == NULL) { return NULL; } @@ -2867,7 +3127,8 @@ struct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address, #endif #if MG_ENABLE_SSL - DBG(("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"), + LOG(LL_DEBUG, + ("%p %s %s,%s,%s", nc, address, (opts.ssl_cert ? opts.ssl_cert : "-"), (opts.ssl_key ? opts.ssl_key : "-"), (opts.ssl_ca_cert ? opts.ssl_ca_cert : "-"))); @@ -2958,10 +3219,7 @@ struct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address, opts.user_data = user_data; #endif - if (callback == NULL) { - MG_SET_PTRPTR(opts.error_string, "handler is required"); - return NULL; - } + if (callback == NULL) callback = mg_ev_handler_empty; MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts); @@ -3113,7 +3371,7 @@ double mg_set_timer(struct mg_connection *c, double timestamp) { DBG(("%p %p %d -> %lu", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0), (unsigned long) timestamp)); if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) { - ((struct mg_connection *) c->priv_2)->ev_timer_time = timestamp; + mg_set_timer((struct mg_connection *) c->priv_2, timestamp); } return result; } @@ -3159,10 +3417,6 @@ double mg_time(void) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net_if_socket.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ #define CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ @@ -3187,10 +3441,6 @@ extern const struct mg_iface_vtable mg_socket_iface_vtable; #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net_if_socks.h" #endif -/* -* Copyright (c) 2014-2017 Cesanta Software Limited -* All rights reserved -*/ #ifndef CS_MONGOOSE_SRC_NET_IF_SOCKS_H_ #define CS_MONGOOSE_SRC_NET_IF_SOCKS_H_ @@ -3253,13 +3503,148 @@ struct mg_iface *mg_find_iface(struct mg_mgr *mgr, } return NULL; } + +double mg_mgr_min_timer(const struct mg_mgr *mgr) { + double min_timer = 0; + struct mg_connection *nc; + for (nc = mgr->active_connections; nc != NULL; nc = nc->next) { + if (nc->ev_timer_time <= 0) continue; + if (min_timer == 0 || nc->ev_timer_time < min_timer) { + min_timer = nc->ev_timer_time; + } + } + return min_timer; +} +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/mg_net_if_null.c" +#endif + +static void mg_null_if_connect_tcp(struct mg_connection *c, + const union socket_address *sa) { + c->flags |= MG_F_CLOSE_IMMEDIATELY; + (void) sa; +} + +static void mg_null_if_connect_udp(struct mg_connection *c) { + c->flags |= MG_F_CLOSE_IMMEDIATELY; +} + +static int mg_null_if_listen_tcp(struct mg_connection *c, + union socket_address *sa) { + (void) c; + (void) sa; + return -1; +} + +static int mg_null_if_listen_udp(struct mg_connection *c, + union socket_address *sa) { + (void) c; + (void) sa; + return -1; +} + +static int mg_null_if_tcp_send(struct mg_connection *c, const void *buf, + size_t len) { + (void) c; + (void) buf; + (void) len; + return -1; +} + +static int mg_null_if_udp_send(struct mg_connection *c, const void *buf, + size_t len) { + (void) c; + (void) buf; + (void) len; + return -1; +} + +int mg_null_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) { + (void) c; + (void) buf; + (void) len; + return -1; +} + +int mg_null_if_udp_recv(struct mg_connection *c, void *buf, size_t len, + union socket_address *sa, size_t *sa_len) { + (void) c; + (void) buf; + (void) len; + (void) sa; + (void) sa_len; + return -1; +} + +static int mg_null_if_create_conn(struct mg_connection *c) { + (void) c; + return 1; +} + +static void mg_null_if_destroy_conn(struct mg_connection *c) { + (void) c; +} + +static void mg_null_if_sock_set(struct mg_connection *c, sock_t sock) { + (void) c; + (void) sock; +} + +static void mg_null_if_init(struct mg_iface *iface) { + (void) iface; +} + +static void mg_null_if_free(struct mg_iface *iface) { + (void) iface; +} + +static void mg_null_if_add_conn(struct mg_connection *c) { + c->sock = INVALID_SOCKET; + c->flags |= MG_F_CLOSE_IMMEDIATELY; +} + +static void mg_null_if_remove_conn(struct mg_connection *c) { + (void) c; +} + +static time_t mg_null_if_poll(struct mg_iface *iface, int timeout_ms) { + struct mg_mgr *mgr = iface->mgr; + struct mg_connection *nc, *tmp; + double now = mg_time(); + /* We basically just run timers and poll. */ + for (nc = mgr->active_connections; nc != NULL; nc = tmp) { + tmp = nc->next; + mg_if_poll(nc, now); + } + (void) timeout_ms; + return (time_t) now; +} + +static void mg_null_if_get_conn_addr(struct mg_connection *c, int remote, + union socket_address *sa) { + (void) c; + (void) remote; + (void) sa; +} + +#define MG_NULL_IFACE_VTABLE \ + { \ + mg_null_if_init, mg_null_if_free, mg_null_if_add_conn, \ + mg_null_if_remove_conn, mg_null_if_poll, mg_null_if_listen_tcp, \ + mg_null_if_listen_udp, mg_null_if_connect_tcp, mg_null_if_connect_udp, \ + mg_null_if_tcp_send, mg_null_if_udp_send, mg_null_if_tcp_recv, \ + mg_null_if_udp_recv, mg_null_if_create_conn, mg_null_if_destroy_conn, \ + mg_null_if_sock_set, mg_null_if_get_conn_addr, \ + } + +const struct mg_iface_vtable mg_null_iface_vtable = MG_NULL_IFACE_VTABLE; + +#if MG_NET_IF == MG_NET_IF_NULL +const struct mg_iface_vtable mg_default_iface_vtable = MG_NULL_IFACE_VTABLE; +#endif /* MG_NET_IF == MG_NET_IF_NULL */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net_if_socket.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_NET_IF_SOCKET @@ -3267,14 +3652,8 @@ struct mg_iface *mg_find_iface(struct mg_mgr *mgr, /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_util.h" */ -#define MG_TCP_RECV_BUFFER_SIZE 1024 -#define MG_UDP_RECV_BUFFER_SIZE 1500 - static sock_t mg_open_listening_socket(union socket_address *sa, int type, int proto); -#if MG_ENABLE_SSL -static void mg_ssl_begin(struct mg_connection *nc); -#endif void mg_set_non_blocking_mode(sock_t sock) { #ifdef _WIN32 @@ -3311,8 +3690,8 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc, #endif rc = connect(nc->sock, &sa->sa, sizeof(sa->sin)); nc->err = rc < 0 && mg_is_error() ? mg_get_errno() : 0; - DBG(("%p sock %d rc %d errno %d err %d", nc, nc->sock, rc, mg_get_errno(), - nc->err)); + DBG(("%p sock %d rc %d errno %d err %d", nc, (int) nc->sock, rc, + mg_get_errno(), nc->err)); } void mg_socket_if_connect_udp(struct mg_connection *nc) { @@ -3343,27 +3722,48 @@ int mg_socket_if_listen_tcp(struct mg_connection *nc, return 0; } -int mg_socket_if_listen_udp(struct mg_connection *nc, - union socket_address *sa) { +static int mg_socket_if_listen_udp(struct mg_connection *nc, + union socket_address *sa) { sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) return (mg_get_errno() ? mg_get_errno() : 1); mg_sock_set(nc, sock); return 0; } -void mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf, - size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); +static int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf, + size_t len) { + int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0); + if (n < 0 && !mg_is_error()) n = 0; + return n; } -void mg_socket_if_udp_send(struct mg_connection *nc, const void *buf, - size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); +static int mg_socket_if_udp_send(struct mg_connection *nc, const void *buf, + size_t len) { + int n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin)); + if (n < 0 && !mg_is_error()) n = 0; + return n; } -void mg_socket_if_recved(struct mg_connection *nc, size_t len) { - (void) nc; - (void) len; +static int mg_socket_if_tcp_recv(struct mg_connection *nc, void *buf, + size_t len) { + int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0); + if (n == 0) { + /* Orderly shutdown of the socket, try flushing output. */ + nc->flags |= MG_F_SEND_AND_CLOSE; + } else if (n < 0 && !mg_is_error()) { + n = 0; + } + return n; +} + +static int mg_socket_if_udp_recv(struct mg_connection *nc, void *buf, + size_t len, union socket_address *sa, + size_t *sa_len) { + socklen_t sa_len_st = *sa_len; + int n = recvfrom(nc->sock, buf, len, 0, &sa->sa, &sa_len_st); + *sa_len = sa_len_st; + if (n < 0 && !mg_is_error()) n = 0; + return n; } int mg_socket_if_create_conn(struct mg_connection *nc) { @@ -3389,7 +3789,9 @@ static int mg_accept_conn(struct mg_connection *lc) { /* NOTE(lsm): on Windows, sock is always > FD_SETSIZE */ sock_t sock = accept(lc->sock, &sa.sa, &sa_len); if (sock == INVALID_SOCKET) { - if (mg_is_error()) DBG(("%p: failed to accept: %d", lc, mg_get_errno())); + if (mg_is_error()) { + DBG(("%p: failed to accept: %d", lc, mg_get_errno())); + } return 0; } nc = mg_if_accept_new_conn(lc); @@ -3400,14 +3802,7 @@ static int mg_accept_conn(struct mg_connection *lc) { DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr), ntohs(sa.sin.sin_port))); mg_sock_set(nc, sock); -#if MG_ENABLE_SSL - if (lc->flags & MG_F_SSL) { - if (mg_ssl_if_conn_accept(nc, lc) != MG_SSL_OK) mg_close_conn(nc); - } else -#endif - { - mg_if_accept_tcp_cb(nc, &sa, sa_len); - } + mg_if_accept_tcp_cb(nc, &sa, sa_len); return 1; } @@ -3458,165 +3853,6 @@ static sock_t mg_open_listening_socket(union socket_address *sa, int type, return sock; } -static void mg_write_to_socket(struct mg_connection *nc) { - struct mbuf *io = &nc->send_mbuf; - int n = 0; - -#if MG_LWIP - /* With LWIP we don't know if the socket is ready */ - if (io->len == 0) return; -#endif - - assert(io->len > 0); - - if (nc->flags & MG_F_UDP) { - int n = - sendto(nc->sock, io->buf, io->len, 0, &nc->sa.sa, sizeof(nc->sa.sin)); - DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, mg_get_errno(), - inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port))); - mg_if_sent_cb(nc, n); - return; - } - -#if MG_ENABLE_SSL - if (nc->flags & MG_F_SSL) { - if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { - n = mg_ssl_if_write(nc, io->buf, io->len); - DBG(("%p %d bytes -> %d (SSL)", nc, n, nc->sock)); - if (n < 0) { - if (n != MG_SSL_WANT_READ && n != MG_SSL_WANT_WRITE) { - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } - return; - } else { - /* Successful SSL operation, clear off SSL wait flags */ - nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE); - } - } else { - mg_ssl_begin(nc); - return; - } - } else -#endif - { - n = (int) MG_SEND_FUNC(nc->sock, io->buf, io->len, 0); - DBG(("%p %d bytes -> %d", nc, n, nc->sock)); - } - - mg_if_sent_cb(nc, n); -} - -MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) { - size_t avail; - if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0; - avail = conn->recv_mbuf_limit - conn->recv_mbuf.len; - return avail > max ? max : avail; -} - -static void mg_handle_tcp_read(struct mg_connection *conn) { - int n = 0; - char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE); - - if (buf == NULL) { - DBG(("OOM")); - return; - } - -#if MG_ENABLE_SSL - if (conn->flags & MG_F_SSL) { - if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) { - /* SSL library may have more bytes ready to read than we ask to read. - * Therefore, read in a loop until we read everything. Without the loop, - * we skip to the next select() cycle which can just timeout. */ - while ((n = mg_ssl_if_read(conn, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) { - DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock)); - mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); - buf = NULL; - if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break; - /* buf has been freed, we need a new one. */ - buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE); - if (buf == NULL) break; - } - MG_FREE(buf); - if (n < 0 && n != MG_SSL_WANT_READ) conn->flags |= MG_F_CLOSE_IMMEDIATELY; - } else { - MG_FREE(buf); - mg_ssl_begin(conn); - return; - } - } else -#endif - { - n = (int) MG_RECV_FUNC(conn->sock, buf, - recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0); - DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock)); - if (n > 0) { - mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); - } else { - MG_FREE(buf); - } - if (n == 0) { - /* Orderly shutdown of the socket, try flushing output. */ - conn->flags |= MG_F_SEND_AND_CLOSE; - } else if (n < 0 && mg_is_error()) { - conn->flags |= MG_F_CLOSE_IMMEDIATELY; - } - } -} - -static int mg_recvfrom(struct mg_connection *nc, union socket_address *sa, - socklen_t *sa_len, char **buf) { - int n; - *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE); - if (*buf == NULL) { - DBG(("Out of memory")); - return -ENOMEM; - } - n = recvfrom(nc->sock, *buf, MG_UDP_RECV_BUFFER_SIZE, 0, &sa->sa, sa_len); - if (n <= 0) { - DBG(("%p recvfrom: %s", nc, strerror(mg_get_errno()))); - MG_FREE(*buf); - } - return n; -} - -static void mg_handle_udp_read(struct mg_connection *nc) { - char *buf = NULL; - union socket_address sa; - socklen_t sa_len = sizeof(sa); - int n = mg_recvfrom(nc, &sa, &sa_len, &buf); - DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr), - ntohs(nc->sa.sin.sin_port))); - mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len); -} - -#if MG_ENABLE_SSL -static void mg_ssl_begin(struct mg_connection *nc) { - int server_side = (nc->listener != NULL); - enum mg_ssl_if_result res = mg_ssl_if_handshake(nc); - DBG(("%p %d res %d", nc, server_side, res)); - - if (res == MG_SSL_OK) { - nc->flags |= MG_F_SSL_HANDSHAKE_DONE; - nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE); - - if (server_side) { - union socket_address sa; - socklen_t sa_len = sizeof(sa); - (void) getpeername(nc->sock, &sa.sa, &sa_len); - mg_if_accept_tcp_cb(nc, &sa, sa_len); - } else { - mg_if_connect_cb(nc, 0); - } - } else if (res != MG_SSL_WANT_READ && res != MG_SSL_WANT_WRITE) { - if (!server_side) { - mg_if_connect_cb(nc, res); - } - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } -} -#endif /* MG_ENABLE_SSL */ - #define _MG_F_FD_CAN_READ 1 #define _MG_F_FD_CAN_WRITE 1 << 1 #define _MG_F_FD_ERROR 1 << 2 @@ -3625,11 +3861,13 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { int worth_logging = fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)); if (worth_logging) { - DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, - fd_flags, nc->flags, (int) nc->recv_mbuf.len, + DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, + (int) nc->sock, fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); } + if (!mg_if_poll(nc, now)) return; + if (nc->flags & MG_F_CONNECTING) { if (fd_flags != 0) { int err = 0; @@ -3650,15 +3888,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { */ err = nc->err; #endif -#if MG_ENABLE_SSL - if ((nc->flags & MG_F_SSL) && err == 0) { - mg_ssl_begin(nc); - } else { - mg_if_connect_cb(nc, err); - } -#else mg_if_connect_cb(nc, err); -#endif } else if (nc->err != 0) { mg_if_connect_cb(nc, nc->err); } @@ -3666,7 +3896,7 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { if (fd_flags & _MG_F_FD_CAN_READ) { if (nc->flags & MG_F_UDP) { - mg_handle_udp_read(nc); + mg_if_can_recv_cb(nc); } else { if (nc->flags & MG_F_LISTENING) { /* @@ -3676,30 +3906,23 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { */ mg_accept_conn(nc); } else { - mg_handle_tcp_read(nc); + mg_if_can_recv_cb(nc); } } } - if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) { - if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) { - mg_write_to_socket(nc); - } - mg_if_poll(nc, (time_t) now); - mg_if_timer(nc, now); - } + if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc); if (worth_logging) { - DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags, - (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); + DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, (int) nc->sock, + nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); } } #if MG_ENABLE_BROADCAST static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) { struct ctl_msg ctl_msg; - int len = - (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); + int len = (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0); size_t dummy = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0); DBG(("read %d from ctl socket", len)); (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */ @@ -3718,7 +3941,7 @@ void mg_socket_if_sock_set(struct mg_connection *nc, sock_t sock) { mg_set_non_blocking_mode(sock); mg_set_close_on_exec(sock); nc->sock = sock; - DBG(("%p %d", nc, sock)); + DBG(("%p %d", nc, (int) sock)); } void mg_socket_if_init(struct mg_iface *iface) { @@ -3805,8 +4028,7 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) { } #endif - if (!(nc->flags & MG_F_WANT_WRITE) && - nc->recv_mbuf.len < nc->recv_mbuf_limit && + if (nc->recv_mbuf.len < nc->recv_mbuf_limit && (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) { mg_add_to_set(nc->sock, &read_set, &max_fd); } @@ -3877,14 +4099,6 @@ time_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) { mg_mgr_handle_conn(nc, fd_flags, now); } - for (nc = mgr->active_connections; nc != NULL; nc = tmp) { - tmp = nc->next; - if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) || - (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) { - mg_close_conn(nc); - } - } - return (time_t) now; } @@ -3910,7 +4124,7 @@ mg_socketpair_accept(sock_t sock, union socket_address *sa, socklen_t sa_len) { } int mg_socketpair(sock_t sp[2], int sock_type) { - union socket_address sa; + union socket_address sa, sa2; sock_t sock; socklen_t len = sizeof(sa.sin); int ret = 0; @@ -3919,18 +4133,20 @@ int mg_socketpair(sock_t sp[2], int sock_type) { (void) memset(&sa, 0, sizeof(sa)); sa.sin.sin_family = AF_INET; - sa.sin.sin_port = htons(0); sa.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ + sa2 = sa; if ((sock = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) { } else if (bind(sock, &sa.sa, len) != 0) { } else if (sock_type == SOCK_STREAM && listen(sock, 1) != 0) { } else if (getsockname(sock, &sa.sa, &len) != 0) { } else if ((sp[0] = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) { - } else if (connect(sp[0], &sa.sa, len) != 0) { + } else if (sock_type == SOCK_STREAM && connect(sp[0], &sa.sa, len) != 0) { } else if (sock_type == SOCK_DGRAM && - (getsockname(sp[0], &sa.sa, &len) != 0 || - connect(sock, &sa.sa, len) != 0)) { + (bind(sp[0], &sa2.sa, len) != 0 || + getsockname(sp[0], &sa2.sa, &len) != 0 || + connect(sp[0], &sa.sa, len) != 0 || + connect(sock, &sa2.sa, len) != 0)) { } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock : mg_socketpair_accept( sock, &sa, len))) == INVALID_SOCKET) { @@ -3991,7 +4207,8 @@ void mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote, mg_socket_if_connect_udp, \ mg_socket_if_tcp_send, \ mg_socket_if_udp_send, \ - mg_socket_if_recved, \ + mg_socket_if_tcp_recv, \ + mg_socket_if_udp_recv, \ mg_socket_if_create_conn, \ mg_socket_if_destroy_conn, \ mg_socket_if_sock_set, \ @@ -4008,10 +4225,6 @@ const struct mg_iface_vtable mg_default_iface_vtable = MG_SOCKET_IFACE_VTABLE; #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net_if_socks.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_SOCKS @@ -4019,18 +4232,35 @@ struct socksdata { char *proxy_addr; /* HOST:PORT of the socks5 proxy server */ struct mg_connection *s; /* Respective connection to the server */ struct mg_connection *c; /* Connection to the client */ - struct mbuf tmp; /* Temporary buffer for sent data */ }; static void socks_if_disband(struct socksdata *d) { LOG(LL_DEBUG, ("disbanding proxy %p %p", d->c, d->s)); - if (d->c) d->c->flags |= MG_F_SEND_AND_CLOSE; - if (d->s) d->s->flags |= MG_F_SEND_AND_CLOSE; - d->c = d->s = NULL; + if (d->c) { + d->c->flags |= MG_F_SEND_AND_CLOSE; + d->c->user_data = NULL; + d->c = NULL; + } + if (d->s) { + d->s->flags |= MG_F_SEND_AND_CLOSE; + d->s->user_data = NULL; + d->s = NULL; + } +} + +static void socks_if_relay(struct mg_connection *s) { + struct socksdata *d = (struct socksdata *) s->user_data; + if (d == NULL || d->c == NULL || !(s->flags & MG_SOCKS_CONNECT_DONE) || + d->s == NULL) { + return; + } + if (s->recv_mbuf.len > 0) mg_if_can_recv_cb(d->c); + if (d->c->send_mbuf.len > 0 && s->send_mbuf.len == 0) mg_if_can_send_cb(d->c); } static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) { struct socksdata *d = (struct socksdata *) c->user_data; + if (d == NULL) return; if (ev == MG_EV_CONNECT) { int res = *(int *) ev_data; if (res == 0) { @@ -4063,6 +4293,7 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) { memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4); memcpy(buf + 8, &d->c->sa.sin.sin_port, 2); mg_send(c, buf, sizeof(buf)); + LOG(LL_DEBUG, ("%p Sent connect request", c)); } /* Process connect request */ if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) && @@ -4075,17 +4306,12 @@ static void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) { } mbuf_remove(&c->recv_mbuf, 10); c->flags |= MG_SOCKS_CONNECT_DONE; - /* Connected. Move sent data from client, if any, to server */ - if (d->s && d->c) { - mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len); - mbuf_free(&d->tmp); - } - } - /* All flags are set, we're in relay mode */ - if ((c->flags & MG_SOCKS_CONNECT_DONE) && d->c && d->s) { - mbuf_append(&d->c->recv_mbuf, d->s->recv_mbuf.buf, d->s->recv_mbuf.len); - mbuf_remove(&d->s->recv_mbuf, d->s->recv_mbuf.len); + LOG(LL_DEBUG, ("%p Connect done %p", c, d->c)); + mg_if_connect_cb(d->c, 0); } + socks_if_relay(c); + } else if (ev == MG_EV_SEND || ev == MG_EV_POLL) { + socks_if_relay(c); } } @@ -4095,7 +4321,7 @@ static void mg_socks_if_connect_tcp(struct mg_connection *c, d->c = c; d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler); d->s->user_data = d; - LOG(LL_DEBUG, ("%p %s", c, d->proxy_addr)); + LOG(LL_DEBUG, ("%p %s %p %p", c, d->proxy_addr, d, d->s)); (void) sa; } @@ -4117,29 +4343,44 @@ static int mg_socks_if_listen_udp(struct mg_connection *c, return -1; } -static void mg_socks_if_tcp_send(struct mg_connection *c, const void *buf, - size_t len) { +static int mg_socks_if_tcp_send(struct mg_connection *c, const void *buf, + size_t len) { + int res; struct socksdata *d = (struct socksdata *) c->iface->data; - LOG(LL_DEBUG, ("%p -> %p %d %d", c, buf, (int) len, (int) c->send_mbuf.len)); - if (d && d->s && d->s->flags & MG_SOCKS_CONNECT_DONE) { - mbuf_append(&d->s->send_mbuf, d->tmp.buf, d->tmp.len); - mbuf_append(&d->s->send_mbuf, buf, len); - mbuf_free(&d->tmp); - } else { - mbuf_append(&d->tmp, buf, len); - } + if (d->s == NULL) return -1; + res = (int) mbuf_append(&d->s->send_mbuf, buf, len); + DBG(("%p -> %d -> %p", c, res, d->s)); + return res; } -static void mg_socks_if_udp_send(struct mg_connection *c, const void *buf, - size_t len) { +static int mg_socks_if_udp_send(struct mg_connection *c, const void *buf, + size_t len) { (void) c; (void) buf; (void) len; + return -1; +} + +int mg_socks_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) { + struct socksdata *d = (struct socksdata *) c->iface->data; + if (d->s == NULL) return -1; + if (len > d->s->recv_mbuf.len) len = d->s->recv_mbuf.len; + if (len > 0) { + memcpy(buf, d->s->recv_mbuf.buf, len); + mbuf_remove(&d->s->recv_mbuf, len); + } + DBG(("%p <- %d <- %p", c, (int) len, d->s)); + return len; } -static void mg_socks_if_recved(struct mg_connection *c, size_t len) { +int mg_socks_if_udp_recv(struct mg_connection *c, void *buf, size_t len, + union socket_address *sa, size_t *sa_len) { (void) c; + (void) buf; (void) len; + (void) sa; + (void) sa_len; + return -1; } static int mg_socks_if_create_conn(struct mg_connection *c) { @@ -4168,7 +4409,6 @@ static void mg_socks_if_free(struct mg_iface *iface) { LOG(LL_DEBUG, ("%p", iface)); if (d != NULL) { socks_if_disband(d); - mbuf_free(&d->tmp); MG_FREE(d->proxy_addr); MG_FREE(d); iface->data = NULL; @@ -4199,14 +4439,15 @@ static void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote, } const struct mg_iface_vtable mg_socks_iface_vtable = { - mg_socks_if_init, mg_socks_if_free, - mg_socks_if_add_conn, mg_socks_if_remove_conn, - mg_socks_if_poll, mg_socks_if_listen_tcp, - mg_socks_if_listen_udp, mg_socks_if_connect_tcp, - mg_socks_if_connect_udp, mg_socks_if_tcp_send, - mg_socks_if_udp_send, mg_socks_if_recved, - mg_socks_if_create_conn, mg_socks_if_destroy_conn, - mg_socks_if_sock_set, mg_socks_if_get_conn_addr, + mg_socks_if_init, mg_socks_if_free, + mg_socks_if_add_conn, mg_socks_if_remove_conn, + mg_socks_if_poll, mg_socks_if_listen_tcp, + mg_socks_if_listen_udp, mg_socks_if_connect_tcp, + mg_socks_if_connect_udp, mg_socks_if_tcp_send, + mg_socks_if_udp_send, mg_socks_if_tcp_recv, + mg_socks_if_udp_recv, mg_socks_if_create_conn, + mg_socks_if_destroy_conn, mg_socks_if_sock_set, + mg_socks_if_get_conn_addr, }; struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) { @@ -4220,10 +4461,6 @@ struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_ssl_if_openssl.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL @@ -4232,6 +4469,12 @@ struct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) { #endif #include +#include +#ifndef KR_VERSION +#include +#endif + +static const char *mg_default_session_id_context = "mongoose"; struct mg_ssl_if_ctx { SSL *ssl; @@ -4240,7 +4483,7 @@ struct mg_ssl_if_ctx { size_t identity_len; }; -void mg_ssl_if_init() { +void mg_ssl_if_init(void) { SSL_library_init(); } @@ -4294,6 +4537,9 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2); SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv3); SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1); + SSL_CTX_set_session_id_context(ctx->ssl_ctx, + (void *) mg_default_session_id_context, + strlen(mg_default_session_id_context)); #ifdef MG_SSL_OPENSSL_NO_COMPRESSION SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_COMPRESSION); #endif @@ -4310,18 +4556,10 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( return MG_SSL_ERROR; } - if (params->ca_cert != NULL && - mg_use_ca_cert(ctx->ssl_ctx, params->ca_cert) != MG_SSL_OK) { - MG_SET_PTRPTR(err_msg, "Invalid SSL CA cert"); - return MG_SSL_ERROR; - } - - if (params->server_name != NULL) { -#ifdef KR_VERSION - SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name); -#else -/* TODO(rojer): Implement server name verification on OpenSSL. */ -#endif + if (params->ca_cert != NULL && + mg_use_ca_cert(ctx->ssl_ctx, params->ca_cert) != MG_SSL_OK) { + MG_SET_PTRPTR(err_msg, "Invalid SSL CA cert"); + return MG_SSL_ERROR; } if (mg_set_cipher_list(ctx->ssl_ctx, params->cipher_suites) != MG_SSL_OK) { @@ -4342,6 +4580,14 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( return MG_SSL_ERROR; } + if (params->server_name != NULL) { +#ifdef KR_VERSION + SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name); +#else + SSL_set_tlsext_host_name(ctx->ssl, params->server_name); +#endif + } + nc->flags |= MG_F_SSL; return MG_SSL_OK; @@ -4351,6 +4597,17 @@ static enum mg_ssl_if_result mg_ssl_if_ssl_err(struct mg_connection *nc, int res) { struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data; int err = SSL_get_error(ctx->ssl, res); + /* + * We've just fetched the last error from the queue. + * Now we need to clear the error queue. If we do not, then the following + * can happen (actually reported): + * - A new connection is accept()-ed with cert error (e.g. self-signed cert) + * - Since all accept()-ed connections share listener's context, + * - *ALL* SSL accepted connection report read error on the next poll cycle. + * Thus a single errored connection can close all the rest, unrelated ones. + * Clearing the error keeps the shared SSL_CTX in an OK state. + */ + ERR_clear_error(); if (err == SSL_ERROR_WANT_READ) return MG_SSL_WANT_READ; if (err == SSL_ERROR_WANT_WRITE) return MG_SSL_WANT_WRITE; DBG(("%p %p SSL error: %d %d", nc, ctx->ssl_ctx, res, err)); @@ -4529,7 +4786,7 @@ static enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl) { : MG_SSL_ERROR); } -#ifndef KR_VERSION +#if !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER) static unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, @@ -4595,10 +4852,10 @@ static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx, (void) ctx; (void) identity; (void) key_str; - /* Krypton does not support PSK. */ + /* Krypton / LibreSSL does not support PSK. */ return MG_SSL_ERROR; } -#endif /* defined(KR_VERSION) */ +#endif /* !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER) */ const char *mg_set_ssl(struct mg_connection *nc, const char *cert, const char *ca_cert) { @@ -4617,18 +4874,17 @@ const char *mg_set_ssl(struct mg_connection *nc, const char *cert, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_ssl_if_mbedtls.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS #include #include +#include #include #include +#include #include +#include static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line, const char *str) { @@ -4638,6 +4894,8 @@ static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line, cs_level = LL_ERROR; break; case 2: + cs_level = LL_INFO; + break; case 3: cs_level = LL_DEBUG; break; @@ -4646,8 +4904,11 @@ static void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line, } /* mbedTLS passes strings with \n at the end, strip it. */ LOG(cs_level, ("%p %.*s", ctx, (int) (strlen(str) - 1), str)); + (void) ctx; + (void) str; (void) file; (void) line; + (void) cs_level; } struct mg_ssl_if_ctx { @@ -4655,14 +4916,20 @@ struct mg_ssl_if_ctx { mbedtls_ssl_context *ssl; mbedtls_x509_crt *cert; mbedtls_pk_context *key; +#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK + char *ca_chain_file; +#else mbedtls_x509_crt *ca_cert; +#endif struct mbuf cipher_suites; + size_t saved_len; }; /* Must be provided by the platform. ctx is struct mg_connection. */ extern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len); -void mg_ssl_if_init() { +void mg_ssl_if_init(void) { + LOG(LL_INFO, ("%s", MBEDTLS_VERSION_STRING_FULL)); } enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc, @@ -4686,9 +4953,11 @@ static enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx, const char *cert); static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx, const char *ciphers); +#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx, const char *identity, const char *key); +#endif enum mg_ssl_if_result mg_ssl_if_conn_init( struct mg_connection *nc, const struct mg_ssl_if_conn_params *params, @@ -4737,11 +5006,13 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( return MG_SSL_ERROR; } +#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED if (mg_ssl_if_mbed_set_psk(ctx, params->psk_identity, params->psk_key) != MG_SSL_OK) { MG_SET_PTRPTR(err_msg, "Invalid PSK settings"); return MG_SSL_ERROR; } +#endif if (!(nc->flags & MG_F_LISTENING)) { ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl)); @@ -4779,40 +5050,41 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( return MG_SSL_OK; } -#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL -int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len); -int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len); -#else -static int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) { +static int mg_ssl_if_mbed_send(void *ctx, const unsigned char *buf, + size_t len) { struct mg_connection *nc = (struct mg_connection *) ctx; - int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0); - LOG(LL_DEBUG, ("%p %d -> %d", nc, (int) len, n)); - if (n >= 0) return n; - n = mg_get_errno(); - return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_WRITE : -1); + int n = nc->iface->vtable->tcp_send(nc, buf, len); + if (n > 0) return n; + if (n == 0) return MBEDTLS_ERR_SSL_WANT_WRITE; + return MBEDTLS_ERR_NET_SEND_FAILED; } -static int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) { +static int mg_ssl_if_mbed_recv(void *ctx, unsigned char *buf, size_t len) { struct mg_connection *nc = (struct mg_connection *) ctx; - int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0); - LOG(LL_DEBUG, ("%p %d <- %d", nc, (int) len, n)); - if (n >= 0) return n; - n = mg_get_errno(); - return ((n == EAGAIN || n == EINPROGRESS) ? MBEDTLS_ERR_SSL_WANT_READ : -1); + int n = nc->iface->vtable->tcp_recv(nc, buf, len); + if (n > 0) return n; + if (n == 0) return MBEDTLS_ERR_SSL_WANT_READ; + return MBEDTLS_ERR_NET_RECV_FAILED; } -#endif static enum mg_ssl_if_result mg_ssl_if_mbed_err(struct mg_connection *nc, int ret) { - if (ret == MBEDTLS_ERR_SSL_WANT_READ) return MG_SSL_WANT_READ; - if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) return MG_SSL_WANT_WRITE; - if (ret != - MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { /* CLOSE_NOTIFY = Normal shutdown */ - LOG(LL_ERROR, ("%p SSL error: %d", nc, ret)); + enum mg_ssl_if_result res = MG_SSL_OK; + if (ret == MBEDTLS_ERR_SSL_WANT_READ) { + res = MG_SSL_WANT_READ; + } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + res = MG_SSL_WANT_WRITE; + } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + LOG(LL_DEBUG, ("%p TLS connection closed by peer", nc)); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + res = MG_SSL_OK; + } else { + LOG(LL_ERROR, ("%p mbedTLS error: -0x%04x", nc, -ret)); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + res = MG_SSL_ERROR; } nc->err = ret; - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - return MG_SSL_ERROR; + return res; } static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) { @@ -4824,18 +5096,17 @@ static void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) { MG_FREE(ctx->key); ctx->key = NULL; } +#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK + MG_FREE(ctx->ca_chain_file); + ctx->ca_chain_file = NULL; +#else if (ctx->ca_cert != NULL) { mbedtls_ssl_conf_ca_chain(ctx->conf, NULL, NULL); -#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK - if (ctx->ca_cert->ca_chain_file != NULL) { - MG_FREE((void *) ctx->ca_cert->ca_chain_file); - ctx->ca_cert->ca_chain_file = NULL; - } -#endif mbedtls_x509_crt_free(ctx->ca_cert); MG_FREE(ctx->ca_cert); ctx->ca_cert = NULL; } +#endif } enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) { @@ -4843,7 +5114,8 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) { int err; /* If bio is not yet set, do it now. */ if (ctx->ssl->p_bio == NULL) { - mbedtls_ssl_set_bio(ctx->ssl, nc, ssl_socket_send, ssl_socket_recv, NULL); + mbedtls_ssl_set_bio(ctx->ssl, nc, mg_ssl_if_mbed_send, mg_ssl_if_mbed_recv, + NULL); } err = mbedtls_ssl_handshake(ctx->ssl); if (err != 0) return mg_ssl_if_mbed_err(nc, err); @@ -4869,20 +5141,35 @@ enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) { return MG_SSL_OK; } -int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) { +int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) { struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data; - int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, buf_size); - DBG(("%p %d -> %d", nc, (int) buf_size, n)); + int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, len); + DBG(("%p %d -> %d", nc, (int) len, n)); if (n < 0) return mg_ssl_if_mbed_err(nc, n); if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY; return n; } -int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) { +int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) { struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data; - int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) data, len); - DBG(("%p %d -> %d", nc, (int) len, n)); - if (n < 0) return mg_ssl_if_mbed_err(nc, n); + /* Per mbedTLS docs, if write returns WANT_READ or WANT_WRITE, the operation + * should be retried with the same data and length. + * Here we assume that the data being pushed will remain the same but the + * amount may grow between calls so we save the length that was used and + * retry. The assumption being that the data itself won't change and won't + * be removed. */ + size_t l = len; + if (ctx->saved_len > 0 && ctx->saved_len < l) l = ctx->saved_len; + int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) buf, l); + DBG(("%p %d,%d,%d -> %d", nc, (int) len, (int) ctx->saved_len, (int) l, n)); + if (n < 0) { + if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) { + ctx->saved_len = len; + } + return mg_ssl_if_mbed_err(nc, n); + } else if (n > 0) { + ctx->saved_len = 0; + } return n; } @@ -4900,11 +5187,11 @@ void mg_ssl_if_conn_free(struct mg_connection *nc) { mbedtls_ssl_free(ctx->ssl); MG_FREE(ctx->ssl); } - mg_ssl_if_mbed_free_certs_and_keys(ctx); if (ctx->conf != NULL) { mbedtls_ssl_config_free(ctx->conf); MG_FREE(ctx->conf); } + mg_ssl_if_mbed_free_certs_and_keys(ctx); mbuf_free(&ctx->cipher_suites); memset(ctx, 0, sizeof(*ctx)); MG_FREE(ctx); @@ -4916,19 +5203,20 @@ static enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx, mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_NONE); return MG_SSL_OK; } - ctx->ca_cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->ca_cert)); - mbedtls_x509_crt_init(ctx->ca_cert); #ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK - ca_cert = strdup(ca_cert); - if (mbedtls_x509_crt_set_ca_chain_file(ctx->ca_cert, ca_cert) != 0) { + ctx->ca_chain_file = strdup(ca_cert); + if (ctx->ca_chain_file == NULL) return MG_SSL_ERROR; + if (mbedtls_ssl_conf_ca_chain_file(ctx->conf, ctx->ca_chain_file, NULL) != 0) { return MG_SSL_ERROR; } #else + ctx->ca_cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->ca_cert)); + mbedtls_x509_crt_init(ctx->ca_cert); if (mbedtls_x509_crt_parse_file(ctx->ca_cert, ca_cert) != 0) { return MG_SSL_ERROR; } -#endif mbedtls_ssl_conf_ca_chain(ctx->conf, ctx->ca_cert, NULL); +#endif mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED); return MG_SSL_OK; } @@ -5035,6 +5323,7 @@ static enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx, return MG_SSL_OK; } +#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx, const char *identity, const char *key_str) { @@ -5071,6 +5360,7 @@ static enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx, } return MG_SSL_OK; } +#endif const char *mg_set_ssl(struct mg_connection *nc, const char *cert, const char *ca_cert) { @@ -5098,10 +5388,6 @@ int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_uri.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_uri.h" */ @@ -5362,10 +5648,6 @@ int mg_assemble_uri(const struct mg_str *scheme, const struct mg_str *user_info, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_HTTP @@ -5491,7 +5773,6 @@ enum mg_http_multipart_stream_state { MPS_BEGIN, MPS_WAITING_FOR_BOUNDARY, MPS_WAITING_FOR_CHUNK, - MPS_GOT_CHUNK, MPS_GOT_BOUNDARY, MPS_FINALIZE, MPS_FINISHED @@ -5503,9 +5784,9 @@ struct mg_http_multipart_stream { const char *var_name; const char *file_name; void *user_data; - int prev_io_len; enum mg_http_multipart_stream_state state; int processing_part; + int data_avail; }; struct mg_reverse_proxy_data { @@ -5542,20 +5823,29 @@ struct mg_http_proto_data { size_t rcvd; /* How many bytes we have received. */ }; -static void mg_http_conn_destructor(void *proto_data); +static void mg_http_proto_data_destructor(void *proto_data); + struct mg_connection *mg_connect_http_base( struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data), struct mg_connect_opts opts, const char *scheme1, const char *scheme2, const char *scheme_ssl1, const char *scheme_ssl2, const char *url, struct mg_str *path, struct mg_str *user_info, struct mg_str *host); -static struct mg_http_proto_data *mg_http_get_proto_data( +MG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data( struct mg_connection *c) { - if (c->proto_data == NULL) { - c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data)); - c->proto_data_destructor = mg_http_conn_destructor; - } + /* If we have proto data from previous connection, flush it. */ + if (c->proto_data != NULL) { + void *pd = c->proto_data; + c->proto_data = NULL; + mg_http_proto_data_destructor(pd); + } + c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data)); + c->proto_data_destructor = mg_http_proto_data_destructor; + return (struct mg_http_proto_data *) c->proto_data; +} +static struct mg_http_proto_data *mg_http_get_proto_data( + struct mg_connection *c) { return (struct mg_http_proto_data *) c->proto_data; } @@ -5592,7 +5882,7 @@ static void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) { current = tmp; } - ep = NULL; + *ep = NULL; } static void mg_http_free_reverse_proxy_data(struct mg_reverse_proxy_data *rpd) { @@ -5611,7 +5901,7 @@ static void mg_http_free_reverse_proxy_data(struct mg_reverse_proxy_data *rpd) { } } -static void mg_http_conn_destructor(void *proto_data) { +static void mg_http_proto_data_destructor(void *proto_data) { struct mg_http_proto_data *pd = (struct mg_http_proto_data *) proto_data; #if MG_ENABLE_FILESYSTEM mg_http_free_proto_data_file(&pd->file); @@ -5684,37 +5974,53 @@ static const struct { MIME_ENTRY("asf", "video/x-ms-asf"), MIME_ENTRY("avi", "video/x-msvideo"), MIME_ENTRY("bmp", "image/bmp"), - {NULL, 0, NULL}}; + {NULL, 0, NULL}, +}; -static struct mg_str mg_get_mime_type(const char *path, const char *dflt, - const struct mg_serve_http_opts *opts) { - const char *ext, *overrides; - size_t i, path_len; - struct mg_str r, k, v; +static struct mg_str mg_get_mime_types_entry(struct mg_str path) { + size_t i; + for (i = 0; mg_static_builtin_mime_types[i].extension != NULL; i++) { + if (path.len < mg_static_builtin_mime_types[i].ext_len + 1) continue; + struct mg_str ext = MG_MK_STR_N(mg_static_builtin_mime_types[i].extension, + mg_static_builtin_mime_types[i].ext_len); + struct mg_str pext = MG_MK_STR_N(path.p + (path.len - ext.len), ext.len); + if (pext.p[-1] == '.' && mg_strcasecmp(ext, pext) == 0) { + return mg_mk_str(mg_static_builtin_mime_types[i].mime_type); + } + } + return mg_mk_str(NULL); +} - path_len = strlen(path); +MG_INTERNAL int mg_get_mime_type_encoding( + struct mg_str path, struct mg_str *type, struct mg_str *encoding, + const struct mg_serve_http_opts *opts) { + const char *ext, *overrides; + struct mg_str k, v; overrides = opts->custom_mime_types; while ((overrides = mg_next_comma_list_entry(overrides, &k, &v)) != NULL) { - ext = path + (path_len - k.len); - if (path_len > k.len && mg_vcasecmp(&k, ext) == 0) { - return v; + ext = path.p + (path.len - k.len); + if (path.len > k.len && mg_vcasecmp(&k, ext) == 0) { + *type = v; + return 1; } } - for (i = 0; mg_static_builtin_mime_types[i].extension != NULL; i++) { - ext = path + (path_len - mg_static_builtin_mime_types[i].ext_len); - if (path_len > mg_static_builtin_mime_types[i].ext_len && ext[-1] == '.' && - mg_casecmp(ext, mg_static_builtin_mime_types[i].extension) == 0) { - r.p = mg_static_builtin_mime_types[i].mime_type; - r.len = strlen(r.p); - return r; + *type = mg_get_mime_types_entry(path); + + /* Check for .html.gz, .js.gz, etc. */ + if (mg_vcmp(type, "application/x-gunzip") == 0) { + struct mg_str path2 = mg_mk_str_n(path.p, path.len - 3); + struct mg_str type2 = mg_get_mime_types_entry(path2); + LOG(LL_ERROR, ("'%.*s' '%.*s' '%.*s'", (int) path.len, path.p, + (int) path2.len, path2.p, (int) type2.len, type2.p)); + if (type2.len > 0) { + *type = type2; + *encoding = mg_mk_str("gzip"); } } - r.p = dflt; - r.len = strlen(r.p); - return r; + return (type->len > 0); } #endif @@ -5811,7 +6117,7 @@ int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req) { } } else { s = mg_skip(s, end, " ", &hm->proto); - if (end - s < 4 || s[3] != ' ') return -1; + if (end - s < 4 || s[0] < '0' || s[0] > '9' || s[3] != ' ') return -1; hm->resp_code = atoi(s); if (hm->resp_code < 100 || hm->resp_code >= 600) return -1; s += 4; @@ -5884,7 +6190,8 @@ static void mg_http_transfer_file_data(struct mg_connection *nc) { /* Rate-limited */ } if (pd->file.sent >= pd->file.cl) { - LOG(LL_DEBUG, ("%p done, %d bytes", nc, (int) pd->file.sent)); + LOG(LL_DEBUG, ("%p done, %d bytes, ka %d", nc, (int) pd->file.sent, + pd->file.keepalive)); if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE; mg_http_free_proto_data_file(&pd->file); } @@ -5930,6 +6237,10 @@ static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data, n *= 16; n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10; i++; + if (i > 6) { + /* Chunk size is unreasonable. */ + return 0; + } } /* Skip new line */ @@ -6016,12 +6327,12 @@ struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc, int matched, matched_max = 0; struct mg_http_endpoint *ep; - if (nc == NULL) { - return NULL; - } + if (nc == NULL) return NULL; pd = mg_http_get_proto_data(nc); + if (pd == NULL) return NULL; + ep = pd->endpoints; while (ep != NULL) { if ((matched = mg_match_prefix_n(ep->uri_pattern, *uri_path)) > 0) { @@ -6093,13 +6404,13 @@ void mg_http_handler(struct mg_connection *nc, int ev, if (ev == MG_EV_CLOSE) { #if MG_ENABLE_HTTP_CGI /* Close associated CGI forwarder connection */ - if (pd->cgi.cgi_nc != NULL) { + if (pd != NULL && pd->cgi.cgi_nc != NULL) { pd->cgi.cgi_nc->user_data = NULL; pd->cgi.cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY; } #endif #if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (pd->mp_stream.boundary != NULL) { + if (pd != NULL && pd->mp_stream.boundary != NULL) { /* * Multipart message is in progress, but connection is closed. * Finish part and request with an error flag. @@ -6107,6 +6418,7 @@ void mg_http_handler(struct mg_connection *nc, int ev, struct mg_http_multipart_part mp; memset(&mp, 0, sizeof(mp)); mp.status = -1; + mp.user_data = pd->mp_stream.user_data; mp.var_name = pd->mp_stream.var_name; mp.file_name = pd->mp_stream.file_name; mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler), @@ -6120,39 +6432,55 @@ 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; deliver_chunk(nc, hm, req_len); mg_http_call_endpoint_handler(nc, ev2, hm); } - pd->rcvd = 0; + if (pd != NULL && pd->endpoint_handler != NULL && + pd->endpoint_handler != nc->handler) { + mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL); + } } #if MG_ENABLE_FILESYSTEM - if (pd->file.fp != NULL) { + if (pd != NULL && pd->file.fp != NULL) { mg_http_transfer_file_data(nc); } #endif mg_call(nc, nc->handler, nc->user_data, ev, ev_data); - 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) { + if (pd != NULL && 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; } + return; + } #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ + if (ev == MG_EV_RECV) { + struct mg_str *s; + + again: req_len = mg_parse_http(io->buf, io->len, hm, is_req); + if (req_len > 0) { + /* New request - new proto data */ + pd = mg_http_create_proto_data(nc); + pd->rcvd = io->len; + } + if (req_len > 0 && (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL && mg_vcasecmp(s, "chunked") == 0) { @@ -6177,16 +6505,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; @@ -6217,7 +6552,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)); } } @@ -6234,6 +6569,7 @@ void mg_http_handler(struct mg_connection *nc, int ev, } } else { /* We did receive all HTTP body. */ + int request_done = 1; int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; char addr[32]; mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), @@ -6244,7 +6580,19 @@ void mg_http_handler(struct mg_connection *nc, int ev, /* Whole HTTP message is fully buffered, call event handler */ mg_http_call_endpoint_handler(nc, trigger_ev, hm); mbuf_remove(io, hm->message.len); - pd->rcvd = 0; + pd->rcvd -= hm->message.len; +#if MG_ENABLE_FILESYSTEM + /* We don't have a generic mechanism of communicating that we are done + * responding to a request (should probably add one). But if we are + * serving + * a file, we are definitely not done. */ + if (pd->file.fp != NULL) request_done = 0; +#endif +#if MG_ENABLE_HTTP_CGI + /* If this is a CGI request, we are not done either. */ + if (pd->cgi.cgi_nc != NULL) request_done = 0; +#endif + if (request_done && io->len > 0) goto again; } } } @@ -6320,8 +6668,9 @@ static void mg_http_multipart_begin(struct mg_connection *nc, #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)); @@ -6331,21 +6680,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; -} - -static int mg_http_multipart_got_chunk(struct mg_connection *c) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - struct mbuf *io = &c->recv_mbuf; - - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, - pd->mp_stream.prev_io_len); - mbuf_remove(io, pd->mp_stream.prev_io_len); - pd->mp_stream.prev_io_len = 0; - pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; - - return 0; + 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) { @@ -6482,21 +6821,26 @@ 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 && pd->mp_stream.prev_io_len == 0) { - pd->mp_stream.prev_io_len = io->len; + if (boundary == NULL) { + 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) io->len > - pd->mp_stream.prev_io_len + pd->mp_stream.boundary_len + 4) { - pd->mp_stream.state = MPS_GOT_CHUNK; - return 1; } 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.prev_io_len = 0; - 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; } @@ -6528,12 +6872,6 @@ static void mg_http_multipart_continue(struct mg_connection *c) { } break; } - case MPS_GOT_CHUNK: { - if (mg_http_multipart_got_chunk(c) == 0) { - return; - } - break; - } case MPS_FINALIZE: { if (mg_http_multipart_finalize(c) == 0) { return; @@ -6541,7 +6879,6 @@ static void mg_http_multipart_continue(struct mg_connection *c) { break; } case MPS_FINISHED: { - mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len); return; } } @@ -6693,8 +7030,11 @@ const char *mg_status_message(int status_code) { void mg_send_response_line_s(struct mg_connection *nc, int status_code, const struct mg_str extra_headers) { - mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code, - mg_status_message(status_code), mg_version_header); + mg_printf(nc, "HTTP/1.1 %d %s\r\n", status_code, + mg_status_message(status_code)); +#ifndef MG_HIDE_SERVER_INFO + mg_printf(nc, "Server: %s\r\n", mg_version_header); +#endif if (extra_headers.len > 0) { mg_printf(nc, "%.*s\r\n", (int) extra_headers.len, extra_headers.p); } @@ -6780,12 +7120,15 @@ static int mg_http_parse_range_header(const struct mg_str *header, int64_t *a, return result; } -void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, - const char *path, const struct mg_str mime_type, - const struct mg_str extra_headers) { +void mg_http_serve_file_internal(struct mg_connection *nc, + struct http_message *hm, const char *path, + struct mg_str mime_type, + struct mg_str encoding, + struct mg_str extra_headers) { struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); cs_stat_t st; - LOG(LL_DEBUG, ("%p [%s] %.*s", nc, path, (int) mime_type.len, mime_type.p)); + LOG(LL_DEBUG, ("%p [%s] %.*s %.*s", nc, path, (int) mime_type.len, + mime_type.p, (int) encoding.len, encoding.p)); if (mg_stat(path, &st) != 0 || (pd->file.fp = mg_fopen(path, "rb")) == NULL) { int code, err = mg_get_errno(); switch (err) { @@ -6824,8 +7167,9 @@ void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, } else { status_code = 206; cl = r2 - r1 + 1; - snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT - "-%" INT64_FMT "/%" INT64_FMT "\r\n", + snprintf(range, sizeof(range), + "Content-Range: bytes %" INT64_FMT "-%" INT64_FMT + "/%" INT64_FMT "\r\n", r1, r1 + cl - 1, (int64_t) st.st_size); #if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ _XOPEN_SOURCE >= 600 @@ -6850,13 +7194,6 @@ void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, mg_http_construct_etag(etag, sizeof(etag), &st); mg_gmt_time_string(current_time, sizeof(current_time), &t); mg_gmt_time_string(last_modified, sizeof(last_modified), &st.st_mtime); - /* - * Content length casted to size_t because: - * 1) that's the maximum buffer size anyway - * 2) ESP8266 RTOS SDK newlib vprintf cannot contain a 64bit arg at non-last - * position - * TODO(mkm): fix ESP8266 RTOS SDK - */ mg_send_response_line_s(nc, status_code, extra_headers); mg_printf(nc, "Date: %s\r\n" @@ -6866,17 +7203,29 @@ void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, "Connection: %s\r\n" "Content-Length: %" SIZE_T_FMT "\r\n" - "%sEtag: %s\r\n\r\n", + "%s" + "Etag: %s\r\n", current_time, last_modified, (int) mime_type.len, mime_type.p, (pd->file.keepalive ? "keep-alive" : "close"), (size_t) cl, range, etag); - + if (encoding.len > 0) { + mg_printf(nc, "Content-Encoding: %.*s\r\n", (int) encoding.len, + encoding.p); + } + mg_send(nc, "\r\n", 2); pd->file.cl = cl; pd->file.type = DATA_FILE; mg_http_transfer_file_data(nc); } } +void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, + const char *path, const struct mg_str mime_type, + const struct mg_str extra_headers) { + mg_http_serve_file_internal(nc, hm, path, mime_type, mg_mk_str(NULL), + extra_headers); +} + static void mg_http_serve_file2(struct mg_connection *nc, const char *path, struct http_message *hm, struct mg_serve_http_opts *opts) { @@ -6886,8 +7235,12 @@ static void mg_http_serve_file2(struct mg_connection *nc, const char *path, return; } #endif - mg_http_serve_file(nc, hm, path, mg_get_mime_type(path, "text/plain", opts), - mg_mk_str(opts->extra_headers)); + struct mg_str type = MG_NULL_STR, encoding = MG_NULL_STR; + if (!mg_get_mime_type_encoding(mg_mk_str(path), &type, &encoding, opts)) { + type = mg_mk_str("text/plain"); + } + mg_http_serve_file_internal(nc, hm, path, type, encoding, + mg_mk_str(opts->extra_headers)); } #endif @@ -7026,7 +7379,7 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) { static void mg_http_parse_header_internal(struct mg_str *hdr, const char *var_name, struct altbuf *ab) { - int ch = ' ', ch1 = ',', n = strlen(var_name); + int ch = ' ', ch1 = ',', ch2 = ';', n = strlen(var_name); const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL; /* Find where variable starts */ @@ -7039,10 +7392,10 @@ static void mg_http_parse_header_internal(struct mg_str *hdr, if (s != NULL && &s[n + 1] < end) { s += n + 1; if (*s == '"' || *s == '\'') { - ch = ch1 = *s++; + ch = ch1 = ch2 = *s++; } p = s; - while (p < end && p[0] != ch && p[0] != ch1) { + while (p < end && p[0] != ch && p[0] != ch1 && p[0] != ch2) { if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++; altbuf_append(ab, *p++); } @@ -7163,7 +7516,7 @@ void cs_md5(char buf[33], ...) { va_list ap; va_start(ap, buf); - while ((p = va_arg(ap, const unsigned char *) ) != NULL) { + while ((p = va_arg(ap, const unsigned char *)) != NULL) { msgs[num_msgs] = p; msg_lens[num_msgs] = va_arg(ap, size_t); num_msgs++; @@ -7278,7 +7631,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, @@ -7292,11 +7645,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; } } @@ -7396,7 +7748,7 @@ static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name, href = mg_url_encode(mg_mk_str(file_name)); mg_printf_http_chunk(nc, "%s%s" - "%s%s\n", + "%s%s", href.p, slash, path, slash, mod, is_dir ? -1 : fsize, size); free((void *) href.p); @@ -7406,7 +7758,7 @@ static void mg_scan_directory(struct mg_connection *nc, const char *dir, const struct mg_serve_http_opts *opts, void (*func)(struct mg_connection *, const char *, cs_stat_t *)) { - char path[MG_MAX_PATH]; + char path[MG_MAX_PATH + 1]; cs_stat_t st; struct dirent *dp; DIR *dirp; @@ -7462,23 +7814,24 @@ static void mg_send_directory_listing(struct mg_connection *nc, const char *dir, mg_printf_http_chunk( nc, - "Index of %.*s%s%s" + "Index of %.*s%s%s" "\n" - "

Index of %.*s

\n" - "" - "\n" - "\n" - "", + "font-family: monospace; }" + "

Index of %.*s

Name" - "Modified" - "Size

" + "" + "" + "" + "" + "", (int) hm->uri.len, hm->uri.p, sort_js_code, sort_js_code2, (int) hm->uri.len, hm->uri.p); mg_scan_directory(nc, dir, opts, mg_print_dir_entry); mg_printf_http_chunk(nc, - "\n" - "
Name" + "ModifiedSize


\n" - "
%s
\n" + "" + "
" + "" + "
%s
" "", mg_version_header); mg_send_http_chunk(nc, "", 0); @@ -7795,13 +8148,13 @@ MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm, *p++ = DIRSEP; /* No NULs and DIRSEPs in the component (percent-encoded). */ for (i = 0; i < component.len; i++, p++) { - if (*p == '\0' || *p == DIRSEP + if (*p == '\0' || + *p == DIRSEP #ifdef _WIN32 /* On Windows, "/" is also accepted, so check for that too. */ - || - *p == '/' + || *p == '/' #endif - ) { + ) { ok = 0; break; } @@ -7891,9 +8244,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 @@ -8000,7 +8355,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) { @@ -8012,7 +8367,9 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, mg_http_send_error(nc, 501, NULL); #endif } else if (mg_is_not_modified(hm, &st)) { - mg_http_send_error(nc, 304, "Not Modified"); + /* Note: not using mg_http_send_error in order to keep connection alive */ + /* Note: passing extra headers allow users to control session cookies */ + mg_send_head(nc, 304, 0, opts->extra_headers); } else { mg_http_serve_file2(nc, index_file ? index_file : path, hm, opts); } @@ -8092,8 +8449,7 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data, case MG_EV_HTTP_PART_BEGIN: { struct mg_http_multipart_part *mp = (struct mg_http_multipart_part *) ev_data; - struct file_upload_state *fus = - (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus)); + struct file_upload_state *fus; struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name)); mp->user_data = NULL; if (lfn.p == NULL || lfn.len == 0) { @@ -8107,13 +8463,18 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data, nc->flags |= MG_F_SEND_AND_CLOSE; return; } + fus = (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus)); + if (fus == NULL) { + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + return; + } fus->lfn = (char *) MG_MALLOC(lfn.len + 1); memcpy(fus->lfn, lfn.p, lfn.len); fus->lfn[lfn.len] = '\0'; if (lfn.p != mp->file_name) MG_FREE((char *) lfn.p); LOG(LL_DEBUG, ("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn)); - fus->fp = mg_fopen(fus->lfn, "w"); + fus->fp = mg_fopen(fus->lfn, "wb"); if (fus->fp == NULL) { mg_printf(nc, "HTTP/1.1 500 Internal Server Error\r\n" @@ -8141,7 +8502,7 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data, #ifdef SPIFFS_ERR_FULL || mg_get_errno() == SPIFFS_ERR_FULL #endif - ) { + ) { mg_printf(nc, "HTTP/1.1 413 Payload Too Large\r\n" "Content-Type: text/plain\r\n" @@ -8178,12 +8539,6 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data, if (mp->status >= 0 && fus->fp != NULL) { LOG(LL_DEBUG, ("%p Uploaded %s (%s), %d bytes", nc, mp->file_name, fus->lfn, (int) fus->num_recd)); - mg_printf(nc, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Connection: close\r\n\r\n" - "Ok, %s - %d bytes.\r\n", - mp->file_name, (int) fus->num_recd); } else { LOG(LL_ERROR, ("Failed to store %s (%s)", mp->file_name, fus->lfn)); /* @@ -8195,6 +8550,15 @@ void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data, MG_FREE(fus->lfn); MG_FREE(fus); mp->user_data = NULL; + /* Don't close the connection yet, there may be more files to come. */ + break; + } + case MG_EV_HTTP_MULTIPART_REQUEST_END: { + mg_printf(nc, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n\r\n" + "Ok.\r\n"); nc->flags |= MG_F_SEND_AND_CLOSE; break; } @@ -8297,8 +8661,9 @@ struct mg_connection *mg_connect_http_opt( if (path.len == 0) path = mg_mk_str("/"); if (host.len == 0) host = mg_mk_str(""); - mg_printf(nc, "%s %.*s HTTP/1.1\r\nHost: %.*s\r\nContent-Length: %" SIZE_T_FMT - "\r\n%.*s%s\r\n%s", + mg_printf(nc, + "%s %.*s HTTP/1.1\r\nHost: %.*s\r\nContent-Length: %" SIZE_T_FMT + "\r\n%.*s%s\r\n%s", (post_data[0] == '\0' ? "GET" : "POST"), (int) path.len, path.p, (int) (path.p - host.p), host.p, strlen(post_data), (int) auth.len, (auth.buf == NULL ? "" : auth.buf), extra_headers, post_data); @@ -8384,6 +8749,7 @@ void mg_register_http_endpoint_opt(struct mg_connection *nc, if (new_ep == NULL) return; pd = mg_http_get_proto_data(nc); + if (pd == NULL) pd = mg_http_create_proto_data(nc); new_ep->uri_pattern = mg_strdup(mg_mk_str(uri_path)); if (opts.auth_domain != NULL && opts.auth_file != NULL) { new_ep->auth_domain = strdup(opts.auth_domain); @@ -8406,7 +8772,7 @@ static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev, #if MG_ENABLE_HTTP_STREAMING_MULTIPART || ev == MG_EV_HTTP_MULTIPART_REQUEST #endif - ) { + ) { struct mg_http_endpoint *ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri); if (ep != NULL) { @@ -8442,10 +8808,6 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http_cgi.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef _WIN32 #include @@ -8922,7 +9284,6 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir, fds[1]) != 0) { - size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len); struct mg_connection *cgi_nc = mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc)); struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc); @@ -8932,8 +9293,8 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, #endif nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS; /* Push POST data to the CGI */ - if (n > 0 && n < nc->recv_mbuf.len) { - mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n); + if (hm->body.len > 0) { + mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, hm->body.len); } mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len); } else { @@ -8959,10 +9320,6 @@ MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http_ssi.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_SSI && MG_ENABLE_FILESYSTEM @@ -9131,7 +9488,7 @@ MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc, const char *path, const struct mg_serve_http_opts *opts) { FILE *fp; - struct mg_str mime_type; + struct mg_str mime_type = MG_NULL_STR, encoding = MG_NULL_STR; DBG(("%p %s", nc, path)); if ((fp = mg_fopen(path, "rb")) == NULL) { @@ -9139,12 +9496,20 @@ MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc, } else { mg_set_close_on_exec((sock_t) fileno(fp)); - mime_type = mg_get_mime_type(path, "text/plain", opts); + if (!mg_get_mime_type_encoding(mg_mk_str(path), &mime_type, &encoding, + opts)) { + mime_type = mg_mk_str("text/plain"); + } mg_send_response_line(nc, 200, opts->extra_headers); mg_printf(nc, "Content-Type: %.*s\r\n" - "Connection: close\r\n\r\n", + "Connection: close\r\n", (int) mime_type.len, mime_type.p); + if (encoding.len > 0) { + mg_printf(nc, "Content-Encoding: %.*s\r\n", (int) encoding.len, + encoding.p); + } + mg_send(nc, "\r\n", 2); mg_send_ssi_file(nc, hm, path, fp, 0, opts); fclose(fp); nc->flags |= MG_F_SEND_AND_CLOSE; @@ -9155,10 +9520,6 @@ MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http_webdav.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV @@ -9427,10 +9788,6 @@ MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http_websocket.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET @@ -9907,6 +10264,8 @@ void mg_send_websocket_handshake3v(struct mg_connection *nc, } mg_printf(nc, "\r\n"); + nc->flags |= MG_F_IS_WEBSOCKET; + mbuf_free(&auth); } @@ -9947,10 +10306,6 @@ struct mg_connection *mg_connect_ws( #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_util.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "common/cs_base64.h" */ /* Amalgamated: #include "mg_internal.h" */ @@ -10093,7 +10448,7 @@ int mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len, goto cleanup; } #else - if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len - 1) == NULL) { + if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len) == NULL) { goto cleanup; } #endif @@ -10262,17 +10617,21 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass, mbuf_append(buf, header_suffix, strlen(header_suffix)); } -struct mg_str mg_url_encode(const struct mg_str src) { - static const char *dont_escape = "._-$,;~()/"; - static const char *hex = "0123456789abcdef"; +struct mg_str mg_url_encode_opt(const struct mg_str src, + const struct mg_str safe, unsigned int flags) { + const char *hex = + (flags & MG_URL_ENCODE_F_UPPERCASE_HEX ? "0123456789ABCDEF" + : "0123456789abcdef"); size_t i = 0; struct mbuf mb; mbuf_init(&mb, src.len); for (i = 0; i < src.len; i++) { const unsigned char c = *((const unsigned char *) src.p + i); - if (isalnum(c) || strchr(dont_escape, c) != NULL) { + if (isalnum(c) || mg_strchr(safe, c) != NULL) { mbuf_append(&mb, &c, 1); + } else if (c == ' ' && (flags & MG_URL_ENCODE_F_SPACE_AS_PLUS)) { + mbuf_append(&mb, "+", 1); } else { mbuf_append(&mb, "%", 1); mbuf_append(&mb, &hex[c >> 4], 1); @@ -10283,13 +10642,13 @@ struct mg_str mg_url_encode(const struct mg_str src) { mbuf_trim(&mb); return mg_mk_str_n(mb.buf, mb.len - 1); } + +struct mg_str mg_url_encode(const struct mg_str src) { + return mg_url_encode_opt(src, mg_mk_str("._-$,;~()/"), 0); +} #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_mqtt.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_MQTT @@ -10298,6 +10657,8 @@ struct mg_str mg_url_encode(const struct mg_str src) { /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_mqtt.h" */ +#define MG_F_MQTT_PING_PENDING MG_F_PROTO_1 + static uint16_t getu16(const char *p) { const uint8_t *up = (const uint8_t *) p; return (up[0] << 8) + up[1]; @@ -10311,8 +10672,8 @@ static const char *scanto(const char *p, struct mg_str *s) { MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) { uint8_t header; - size_t len = 0, len_len = 0; - const char *p, *end; + uint32_t len, len_len; /* must be 32-bit, see #1055 */ + const char *p, *end, *eop = &io->buf[io->len]; unsigned char lc = 0; int cmd; @@ -10323,18 +10684,16 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) { /* decode mqtt variable length */ len = len_len = 0; p = io->buf + 1; - while ((size_t)(p - io->buf) < io->len) { + while (p < eop) { lc = *((const unsigned char *) p++); len += (lc & 0x7f) << 7 * len_len; len_len++; if (!(lc & 0x80)) break; - if (len_len > 4) return MG_MQTT_ERROR_MALFORMED_MSG; + if (len_len > sizeof(len)) return MG_MQTT_ERROR_MALFORMED_MSG; } end = p + len; - if (lc & 0x80 || len > (io->len - (p - io->buf))) { - return MG_MQTT_ERROR_INCOMPLETE_MSG; - } + if (lc & 0x80 || end > eop) return MG_MQTT_ERROR_INCOMPLETE_MSG; mm->cmd = cmd; mm->qos = MG_MQTT_GET_QOS(header); @@ -10388,7 +10747,9 @@ MG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) { case MG_MQTT_CMD_PUBREL: case MG_MQTT_CMD_PUBCOMP: case MG_MQTT_CMD_SUBACK: + if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG; mm->message_id = getu16(p); + p += 2; break; case MG_MQTT_CMD_PUBLISH: { p = scanto(p, &mm->topic); @@ -10460,6 +10821,10 @@ static void mqtt_handler(struct mg_connection *nc, int ev, } break; } + if (mm.cmd == MG_MQTT_CMD_PINGRESP) { + LOG(LL_DEBUG, ("Recv PINGRESP")); + nc->flags &= ~MG_F_MQTT_PING_PENDING; + } nc->handler(nc, MG_MQTT_EVENT_BASE + mm.cmd, &mm MG_UD_ARG(user_data)); mbuf_remove(io, len); @@ -10470,10 +10835,26 @@ static void mqtt_handler(struct mg_connection *nc, int ev, struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data; double now = mg_time(); - if (pd->keep_alive > 0 && pd->last_control_time > 0 && - (now - pd->last_control_time) > pd->keep_alive) { - LOG(LL_DEBUG, ("Send PINGREQ")); - mg_mqtt_ping(nc); + if (pd->keep_alive > 0 && pd->last_control_time > 0) { + double diff = (now - pd->last_control_time); + if (diff > pd->keep_alive) { + if (diff < 1500000000) { + if (!(nc->flags & MG_F_MQTT_PING_PENDING)) { + LOG(LL_DEBUG, ("Send PINGREQ")); + nc->flags |= MG_F_MQTT_PING_PENDING; + mg_mqtt_ping(nc); + } else { + LOG(LL_DEBUG, ("Ping timeout")); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } + } else { + /* Wall time has just been set. Avoid immediate ping, + * more likely than not it is not needed. The standard allows for + * 1.5X interval for ping requests, so even if were just about to + * send one, we should be ok waiting 0.4X more. */ + pd->last_control_time = now - pd->keep_alive * 0.6; + } + } } break; } @@ -10484,26 +10865,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) { @@ -10516,18 +10915,13 @@ void mg_set_protocol_mqtt(struct mg_connection *nc) { nc->proto_data_destructor = mg_mqtt_proto_data_destructor; } -static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd, - uint8_t flags, size_t len) { +static void mg_send_mqtt_header(struct mg_connection *nc, uint8_t cmd, + uint8_t flags, size_t len) { struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data; - size_t off = nc->send_mbuf.len - len; - uint8_t header = cmd << 4 | (uint8_t) flags; - uint8_t buf[1 + sizeof(size_t)]; uint8_t *vlen = &buf[1]; - assert(nc->send_mbuf.len >= len); - - buf[0] = header; + buf[0] = (cmd << 4) | flags; /* mqtt variable length encoding */ do { @@ -10537,7 +10931,7 @@ static void mg_mqtt_prepend_header(struct mg_connection *nc, uint8_t cmd, vlen++; } while (len > 0); - mbuf_insert(&nc->send_mbuf, off, buf, vlen - buf); + mg_send(nc, buf, vlen - buf); pd->last_control_time = mg_time(); } @@ -10548,11 +10942,16 @@ void mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) { void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id, struct mg_send_mqtt_handshake_opts opts) { - uint16_t hlen, nlen, rem_len = 0; struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data; + uint16_t id_len = 0, wt_len = 0, wm_len = 0, user_len = 0, pw_len = 0; + uint16_t netbytes; + size_t total_len; - mg_send(nc, "\00\04MQTT\04", 7); - rem_len += 7; + if (client_id != NULL) { + id_len = strlen(client_id); + } + + total_len = 7 + 1 + 2 + 2 + id_len; if (opts.user_name != NULL) { opts.flags |= MG_MQTT_HAS_USER_NAME; @@ -10561,56 +10960,58 @@ void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id, opts.flags |= MG_MQTT_HAS_PASSWORD; } if (opts.will_topic != NULL && opts.will_message != NULL) { + wt_len = strlen(opts.will_topic); + wm_len = strlen(opts.will_message); opts.flags |= MG_MQTT_HAS_WILL; } if (opts.keep_alive == 0) { opts.keep_alive = 60; } + if (opts.flags & MG_MQTT_HAS_WILL) { + total_len += 2 + wt_len + 2 + wm_len; + } + if (opts.flags & MG_MQTT_HAS_USER_NAME) { + user_len = strlen(opts.user_name); + total_len += 2 + user_len; + } + if (opts.flags & MG_MQTT_HAS_PASSWORD) { + pw_len = strlen(opts.password); + total_len += 2 + pw_len; + } + + mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNECT, 0, total_len); + mg_send(nc, "\00\04MQTT\04", 7); mg_send(nc, &opts.flags, 1); - rem_len += 1; - nlen = htons(opts.keep_alive); - mg_send(nc, &nlen, 2); - rem_len += 2; + netbytes = htons(opts.keep_alive); + mg_send(nc, &netbytes, 2); - hlen = strlen(client_id); - nlen = htons((uint16_t) hlen); - mg_send(nc, &nlen, 2); - mg_send(nc, client_id, hlen); - rem_len += 2 + hlen; + netbytes = htons(id_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, client_id, id_len); if (opts.flags & MG_MQTT_HAS_WILL) { - hlen = strlen(opts.will_topic); - nlen = htons((uint16_t) hlen); - mg_send(nc, &nlen, 2); - mg_send(nc, opts.will_topic, hlen); - rem_len += 2 + hlen; + netbytes = htons(wt_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, opts.will_topic, wt_len); - hlen = strlen(opts.will_message); - nlen = htons((uint16_t) hlen); - mg_send(nc, &nlen, 2); - mg_send(nc, opts.will_message, hlen); - rem_len += 2 + hlen; + netbytes = htons(wm_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, opts.will_message, wm_len); } if (opts.flags & MG_MQTT_HAS_USER_NAME) { - hlen = strlen(opts.user_name); - nlen = htons((uint16_t) hlen); - mg_send(nc, &nlen, 2); - mg_send(nc, opts.user_name, hlen); - rem_len += 2 + hlen; + netbytes = htons(user_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, opts.user_name, user_len); } if (opts.flags & MG_MQTT_HAS_PASSWORD) { - hlen = strlen(opts.password); - nlen = htons((uint16_t) hlen); - mg_send(nc, &nlen, 2); - mg_send(nc, opts.password, hlen); - rem_len += 2 + hlen; + netbytes = htons(pw_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, opts.password, pw_len); } - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNECT, 0, rem_len); - if (pd != NULL) { pd->keep_alive = opts.keep_alive; } @@ -10619,40 +11020,52 @@ void mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id, void mg_mqtt_publish(struct mg_connection *nc, const char *topic, uint16_t message_id, int flags, const void *data, size_t len) { - size_t old_len = nc->send_mbuf.len; + uint16_t netbytes; + uint16_t topic_len = strlen(topic); + + size_t total_len = 2 + topic_len + len; + if (MG_MQTT_GET_QOS(flags) > 0) { + total_len += 2; + } + + mg_send_mqtt_header(nc, MG_MQTT_CMD_PUBLISH, flags, total_len); - uint16_t topic_len = htons((uint16_t) strlen(topic)); - uint16_t message_id_net = htons(message_id); + netbytes = htons(topic_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, topic, topic_len); - mg_send(nc, &topic_len, 2); - mg_send(nc, topic, strlen(topic)); if (MG_MQTT_GET_QOS(flags) > 0) { - mg_send(nc, &message_id_net, 2); + netbytes = htons(message_id); + mg_send(nc, &netbytes, 2); } - mg_send(nc, data, len); - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PUBLISH, flags, - nc->send_mbuf.len - old_len); + mg_send(nc, data, len); } void mg_mqtt_subscribe(struct mg_connection *nc, const struct mg_mqtt_topic_expression *topics, size_t topics_len, uint16_t message_id) { - size_t old_len = nc->send_mbuf.len; - - uint16_t message_id_n = htons(message_id); + uint16_t netbytes; size_t i; + uint16_t topic_len; + size_t total_len = 2; - mg_send(nc, (char *) &message_id_n, 2); for (i = 0; i < topics_len; i++) { - uint16_t topic_len_n = htons((uint16_t) strlen(topics[i].topic)); - mg_send(nc, &topic_len_n, 2); - mg_send(nc, topics[i].topic, strlen(topics[i].topic)); - mg_send(nc, &topics[i].qos, 1); + total_len += 2 + strlen(topics[i].topic) + 1; } - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1), - nc->send_mbuf.len - old_len); + mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1), total_len); + + netbytes = htons(message_id); + mg_send(nc, (char *) &netbytes, 2); + + for (i = 0; i < topics_len; i++) { + topic_len = strlen(topics[i].topic); + netbytes = htons(topic_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, topics[i].topic, topic_len); + mg_send(nc, &topics[i].qos, 1); + } } int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg, @@ -10672,27 +11085,33 @@ int mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg, void mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics, size_t topics_len, uint16_t message_id) { - size_t old_len = nc->send_mbuf.len; - - uint16_t message_id_n = htons(message_id); + uint16_t netbytes; size_t i; + uint16_t topic_len; + size_t total_len = 2; - mg_send(nc, (char *) &message_id_n, 2); for (i = 0; i < topics_len; i++) { - uint16_t topic_len_n = htons((uint16_t) strlen(topics[i])); - mg_send(nc, &topic_len_n, 2); - mg_send(nc, topics[i], strlen(topics[i])); + total_len += 2 + strlen(topics[i]); } - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1), - nc->send_mbuf.len - old_len); + mg_send_mqtt_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1), total_len); + + netbytes = htons(message_id); + mg_send(nc, (char *) &netbytes, 2); + + for (i = 0; i < topics_len; i++) { + topic_len = strlen(topics[i]); + netbytes = htons(topic_len); + mg_send(nc, &netbytes, 2); + mg_send(nc, topics[i], topic_len); + } } void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) { uint8_t unused = 0; + mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNACK, 0, 2); mg_send(nc, &unused, 1); mg_send(nc, &return_code, 1); - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_CONNACK, 0, 2); } /* @@ -10702,10 +11121,13 @@ void mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) { */ static void mg_send_mqtt_short_command(struct mg_connection *nc, uint8_t cmd, uint16_t message_id) { - uint16_t message_id_net = htons(message_id); + uint16_t netbytes; uint8_t flags = (cmd == MG_MQTT_CMD_PUBREL ? 2 : 0); - mg_send(nc, &message_id_net, 2); - mg_mqtt_prepend_header(nc, cmd, flags, 2 /* len */); + + mg_send_mqtt_header(nc, cmd, flags, 2 /* len */); + + netbytes = htons(message_id); + mg_send(nc, &netbytes, 2); } void mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id) { @@ -10727,12 +11149,16 @@ void mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id) { void mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len, uint16_t message_id) { size_t i; - uint16_t message_id_net = htons(message_id); - mg_send(nc, &message_id_net, 2); + uint16_t netbytes; + + mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len); + + netbytes = htons(message_id); + mg_send(nc, &netbytes, 2); + for (i = 0; i < qoss_len; i++) { mg_send(nc, &qoss[i], 1); } - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len); } void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) { @@ -10740,25 +11166,21 @@ void mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) { } void mg_mqtt_ping(struct mg_connection *nc) { - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0); + mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0); } void mg_mqtt_pong(struct mg_connection *nc) { - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0); + mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0); } void mg_mqtt_disconnect(struct mg_connection *nc) { - mg_mqtt_prepend_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0); + mg_send_mqtt_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0); } #endif /* MG_ENABLE_MQTT */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_mqtt_server.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_mqtt_server.h" */ @@ -10952,10 +11374,6 @@ struct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_dns.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_DNS @@ -11039,35 +11457,39 @@ int mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg) { return mbuf_append(io, begin, end - begin); } -int mg_dns_encode_name(struct mbuf *io, const char *name, size_t len) { +int mg_dns_encode_name_s(struct mbuf *io, struct mg_str name) { const char *s; unsigned char n; size_t pos = io->len; do { - if ((s = strchr(name, '.')) == NULL) { - s = name + len; + if ((s = mg_strchr(name, '.')) == NULL) { + s = name.p + name.len; } - if (s - name > 127) { + if (s - name.p > 127) { return -1; /* TODO(mkm) cover */ } - n = s - name; /* chunk length */ + n = s - name.p; /* chunk length */ mbuf_append(io, &n, 1); /* send length */ - mbuf_append(io, name, n); + mbuf_append(io, name.p, n); - if (*s == '.') { + if (n < name.len && *s == '.') { n++; } - name += n; - len -= n; - } while (*s != '\0'); + name.p += n; + name.len -= n; + } while (name.len > 0); mbuf_append(io, "\0", 1); /* Mark end of host name */ return io->len - pos; } +int mg_dns_encode_name(struct mbuf *io, const char *name, size_t len) { + return mg_dns_encode_name_s(io, mg_mk_str_n(name, len)); +} + int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr, const char *name, size_t nlen, const void *rdata, size_t rlen) { @@ -11332,10 +11754,6 @@ void mg_set_protocol_dns(struct mg_connection *nc) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_dns_server.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_DNS_SERVER @@ -11406,10 +11824,6 @@ int mg_dns_reply_record(struct mg_dns_reply *reply, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_resolv.c" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_ASYNC_RESOLVER @@ -11556,12 +11970,13 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, time_t now = (time_t) mg_time(); struct mg_resolve_async_request *req; struct mg_dns_message *msg; - int first = 0; #if !MG_ENABLE_CALLBACK_USERDATA void *user_data = nc->user_data; #endif - if (ev != MG_EV_POLL) DBG(("ev=%d user_data=%p", ev, user_data)); + if (ev != MG_EV_POLL) { + DBG(("ev=%d user_data=%p", ev, user_data)); + } req = (struct mg_resolve_async_request *) user_data; @@ -11570,17 +11985,16 @@ static void mg_resolve_async_eh(struct mg_connection *nc, int ev, } switch (ev) { - case MG_EV_CONNECT: - /* don't depend on timer not being at epoch for sending out first req */ - first = 1; - /* fallthrough */ case MG_EV_POLL: if (req->retries > req->max_retries) { req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT; nc->flags |= MG_F_CLOSE_IMMEDIATELY; break; } - if (first || now - req->last_time >= req->timeout) { + if (nc->flags & MG_F_CONNECTING) break; + /* fallthrough */ + case MG_EV_CONNECT: + if (req->retries == 0 || now - req->last_time >= req->timeout) { mg_send_dns_query(nc, req->name, req->query); req->last_time = now; req->retries++; @@ -11701,22 +12115,6 @@ void mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_coap.c" #endif -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_coap.h" */ @@ -12301,10 +12699,6 @@ int mg_set_protocol_coap(struct mg_connection *nc) { #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_sntp.c" #endif -/* - * Copyright (c) 2016 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_sntp.h" */ @@ -12592,10 +12986,6 @@ struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_socks.c" #endif -/* - * Copyright (c) 2017 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_SOCKS @@ -12754,10 +13144,6 @@ void mg_set_protocol_socks(struct mg_connection *c) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/cc3200/cc3200_libc.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if CS_PLATFORM == CS_P_CC3200 @@ -12860,10 +13246,6 @@ int _isatty(int fd) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/msp432/msp432_libc.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if CS_PLATFORM == CS_P_MSP432 @@ -12881,10 +13263,6 @@ int gettimeofday(struct timeval *tp, void *tzp) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/nrf5/nrf5_libc.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if (CS_PLATFORM == CS_P_NRF51 || CS_PLATFORM == CS_P_NRF52) && \ defined(__ARMCC_VERSION) @@ -12898,10 +13276,6 @@ int gettimeofday(struct timeval *tp, void *tzp) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_fs_slfs.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_ #define CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_ @@ -12927,7 +13301,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) */ @@ -12935,10 +13311,6 @@ void fs_slfs_set_new_file_size(const char *name, size_t size); #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_fs_slfs.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ /* Standard libc interface to TI SimpleLink FS. */ @@ -12956,14 +13328,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 @@ -12979,9 +13351,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 { @@ -12991,7 +13365,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)); @@ -13032,7 +13409,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); @@ -13047,24 +13430,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 + } + uint32_t *token = NULL; + if (foi != NULL) { + am |= foi->flags; + token = foi->token; } - 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)); + 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; @@ -13168,26 +13554,52 @@ 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) */ #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_fs.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_NET_IF == MG_NET_IF_SIMPLELINK && \ (defined(MG_FS_SLFS) || defined(MG_FS_SPIFFS)) @@ -13598,10 +14010,6 @@ int sl_fs_init(void) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_socket.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_NET_IF == MG_NET_IF_SIMPLELINK @@ -13704,10 +14112,6 @@ void mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_net_if.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_ #define CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_ @@ -13732,10 +14136,6 @@ extern const struct mg_iface_vtable mg_simplelink_iface_vtable; #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_net_if.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ /* Amalgamated: #include "common/platforms/simplelink/sl_net_if.h" */ @@ -13751,7 +14151,7 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc, union socket_address *sa, int type, int proto); -void mg_set_non_blocking_mode(sock_t sock) { +static void mg_set_non_blocking_mode(sock_t sock) { SlSockNonblocking_t opt; #if SL_MAJOR_VERSION_NUM < 2 opt.NonblockingEnabled = 1; @@ -13765,17 +14165,19 @@ static int mg_is_error(int n) { return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN); } -void mg_sl_if_connect_tcp(struct mg_connection *nc, - const union socket_address *sa) { +static void mg_sl_if_connect_tcp(struct mg_connection *nc, + const union socket_address *sa) { int proto = 0; +#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET; +#endif sock_t sock = sl_Socket(AF_INET, SOCK_STREAM, proto); if (sock < 0) { nc->err = sock; goto out; } mg_sock_set(nc, sock); -#if MG_ENABLE_SSL +#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK nc->err = sl_set_ssl_opts(sock, nc); if (nc->err != 0) goto out; #endif @@ -13785,7 +14187,7 @@ void mg_sl_if_connect_tcp(struct mg_connection *nc, ntohs(sa->sin.sin_port), nc->sock, proto, nc->err)); } -void mg_sl_if_connect_udp(struct mg_connection *nc) { +static void mg_sl_if_connect_udp(struct mg_connection *nc) { sock_t sock = sl_Socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { nc->err = sock; @@ -13795,7 +14197,8 @@ void mg_sl_if_connect_udp(struct mg_connection *nc) { nc->err = 0; } -int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) { +static int mg_sl_if_listen_tcp(struct mg_connection *nc, + union socket_address *sa) { int proto = 0; if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET; sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto); @@ -13804,27 +14207,50 @@ int mg_sl_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) { return 0; } -int mg_sl_if_listen_udp(struct mg_connection *nc, union socket_address *sa) { +static int mg_sl_if_listen_udp(struct mg_connection *nc, + union socket_address *sa) { sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0); if (sock == INVALID_SOCKET) return (errno ? errno : 1); mg_sock_set(nc, sock); return 0; } -void mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); +static int mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf, + size_t len) { + int n = (int) sl_Send(nc->sock, buf, len, 0); + if (n < 0 && !mg_is_error(n)) n = 0; + return n; } -void mg_sl_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); +static int mg_sl_if_udp_send(struct mg_connection *nc, const void *buf, + size_t len) { + int n = sl_SendTo(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin)); + if (n < 0 && !mg_is_error(n)) n = 0; + return n; } -void mg_sl_if_recved(struct mg_connection *nc, size_t len) { - (void) nc; - (void) len; +static int mg_sl_if_tcp_recv(struct mg_connection *nc, void *buf, size_t len) { + int n = sl_Recv(nc->sock, buf, len, 0); + if (n == 0) { + /* Orderly shutdown of the socket, try flushing output. */ + nc->flags |= MG_F_SEND_AND_CLOSE; + } else if (n < 0 && !mg_is_error(n)) { + n = 0; + } + return n; } -int mg_sl_if_create_conn(struct mg_connection *nc) { +static int mg_sl_if_udp_recv(struct mg_connection *nc, void *buf, size_t len, + union socket_address *sa, size_t *sa_len) { + SlSocklen_t sa_len_t = *sa_len; + int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0, + (SlSockAddr_t *) sa, &sa_len_t); + *sa_len = sa_len_t; + if (n < 0 && !mg_is_error(n)) n = 0; + return n; +} + +static int mg_sl_if_create_conn(struct mg_connection *nc) { (void) nc; return 1; } @@ -13855,7 +14281,6 @@ static int mg_accept_conn(struct mg_connection *lc) { DBG(("%p conn from %s:%d", nc, inet_ntoa(sa.sin.sin_addr), ntohs(sa.sin.sin_port))); mg_sock_set(nc, sock); - if (nc->flags & MG_F_SSL) nc->flags |= MG_F_SSL_HANDSHAKE_DONE; mg_if_accept_tcp_cb(nc, &sa, sa_len); return 1; } @@ -13869,11 +14294,11 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc, (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6); sock_t sock = sl_Socket(sa->sa.sa_family, type, proto); if (sock < 0) return sock; +#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK + if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean; +#endif if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean; if (type != SOCK_DGRAM) { -#if MG_ENABLE_SSL - if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean; -#endif if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean; } mg_set_non_blocking_mode(sock); @@ -13885,86 +14310,18 @@ static sock_t mg_open_listening_socket(struct mg_connection *nc, return sock; } -static void mg_write_to_socket(struct mg_connection *nc) { - struct mbuf *io = &nc->send_mbuf; - int n = 0; - - if (nc->flags & MG_F_UDP) { - n = sl_SendTo(nc->sock, io->buf, io->len, 0, &nc->sa.sa, - sizeof(nc->sa.sin)); - DBG(("%p %d %d %d %s:%hu", nc, nc->sock, n, errno, - inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port))); - } else { - n = (int) sl_Send(nc->sock, io->buf, io->len, 0); - DBG(("%p %d bytes -> %d", nc, n, nc->sock)); - } - - if (n > 0) { - mg_if_sent_cb(nc, n); - } else if (n < 0 && mg_is_error(n)) { - /* Something went wrong, drop the connection. */ - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } -} - -MG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) { - size_t avail; - if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0; - avail = conn->recv_mbuf_limit - conn->recv_mbuf.len; - return avail > max ? max : avail; -} - -static void mg_handle_tcp_read(struct mg_connection *conn) { - int n = 0; - char *buf = (char *) MG_MALLOC(MG_TCP_RECV_BUFFER_SIZE); - - if (buf == NULL) { - DBG(("OOM")); - return; - } - - n = (int) sl_Recv(conn->sock, buf, - recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0); - DBG(("%p %d bytes <- %d", conn, n, conn->sock)); - if (n > 0) { - mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); - } else { - MG_FREE(buf); - } - if (n == 0) { - /* Orderly shutdown of the socket, try flushing output. */ - conn->flags |= MG_F_SEND_AND_CLOSE; - } else if (mg_is_error(n)) { - conn->flags |= MG_F_CLOSE_IMMEDIATELY; - } -} - -static void mg_handle_udp_read(struct mg_connection *nc) { - char *buf = (char *) MG_MALLOC(MG_UDP_RECV_BUFFER_SIZE); - if (buf == NULL) return; - union socket_address sa; - socklen_t sa_len = sizeof(sa); - int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0, - (SlSockAddr_t *) &sa, &sa_len); - DBG(("%p %d bytes from %s:%d", nc, n, inet_ntoa(nc->sa.sin.sin_addr), - ntohs(nc->sa.sin.sin_port))); - if (n > 0) { - mg_if_recv_udp_cb(nc, buf, n, &sa, sa_len); - } else { - MG_FREE(buf); - } -} - #define _MG_F_FD_CAN_READ 1 #define _MG_F_FD_CAN_WRITE 1 << 1 #define _MG_F_FD_ERROR 1 << 2 void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { - DBG(("%p fd=%d fd_flags=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, + DBG(("%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock, fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); + if (!mg_if_poll(nc, now)) return; + if (nc->flags & MG_F_CONNECTING) { - if (nc->flags & MG_F_UDP || nc->err != SL_ERROR_BSD_EALREADY) { + if ((nc->flags & MG_F_UDP) || nc->err != SL_ERROR_BSD_EALREADY) { mg_if_connect_cb(nc, nc->err); } else { /* In SimpleLink, to get status of non-blocking connect() we need to wait @@ -13986,9 +14343,6 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { ) { nc->err = 0; } - if (nc->flags & MG_F_SSL && nc->err == 0) { - nc->flags |= MG_F_SSL_HANDSHAKE_DONE; - } mg_if_connect_cb(nc, nc->err); } } @@ -13998,28 +14352,21 @@ void mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) { if (fd_flags & _MG_F_FD_CAN_READ) { if (nc->flags & MG_F_UDP) { - mg_handle_udp_read(nc); + mg_if_can_recv_cb(nc); } else { if (nc->flags & MG_F_LISTENING) { mg_accept_conn(nc); } else { - mg_handle_tcp_read(nc); + mg_if_can_recv_cb(nc); } } } - if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) { - if ((fd_flags & _MG_F_FD_CAN_WRITE) && nc->send_mbuf.len > 0) { - mg_write_to_socket(nc); - } - - if (!(fd_flags & (_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE))) { - mg_if_poll(nc, now); - } - mg_if_timer(nc, now); + if (fd_flags & _MG_F_FD_CAN_WRITE) { + mg_if_can_send_cb(nc); } - DBG(("%p after fd=%d nc_flags=%lu rmbl=%d smbl=%d", nc, nc->sock, nc->flags, + DBG(("%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d", nc, nc->sock, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len)); } @@ -14140,14 +14487,6 @@ time_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) { mg_mgr_handle_conn(nc, fd_flags, now); } - for (nc = mgr->active_connections; nc != NULL; nc = tmp) { - tmp = nc->next; - if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) || - (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) { - mg_close_conn(nc); - } - } - return now; } @@ -14198,7 +14537,8 @@ void sl_restart_cb(struct mg_mgr *mgr) { mg_sl_if_connect_udp, \ mg_sl_if_tcp_send, \ mg_sl_if_udp_send, \ - mg_sl_if_recved, \ + mg_sl_if_tcp_recv, \ + mg_sl_if_udp_recv, \ mg_sl_if_create_conn, \ mg_sl_if_destroy_conn, \ mg_sl_if_sock_set, \ @@ -14215,10 +14555,6 @@ const struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE; #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/sl_ssl_if.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK @@ -14271,6 +14607,31 @@ enum mg_ssl_if_result mg_ssl_if_conn_init( return MG_SSL_OK; } +enum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc, + struct mg_connection *lc) { + /* SimpleLink does everything for us, nothing for us to do. */ + (void) nc; + (void) lc; + return MG_SSL_OK; +} + +enum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) { + /* SimpleLink has already performed the handshake, nothing to do. */ + return MG_SSL_OK; +} + +int mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) { + /* SimpelLink handles TLS, so this is just a pass-through. */ + int n = nc->iface->vtable->tcp_recv(nc, buf, len); + if (n == 0) nc->flags |= MG_F_WANT_READ; + return n; +} + +int mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) { + /* SimpelLink handles TLS, so this is just a pass-through. */ + return nc->iface->vtable->tcp_send(nc, buf, len); +} + void mg_ssl_if_conn_close_notify(struct mg_connection *nc) { /* Nothing to do */ (void) nc; @@ -14295,9 +14656,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]; @@ -14371,56 +14732,59 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc) { const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data; DBG(("%p ssl ctx: %p", nc, ctx)); - if (ctx != NULL) { - DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"), - (ctx->ssl_key ? ctx->ssl_cert : "-"), - (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"), - (ctx->ssl_server_name ? ctx->ssl_server_name : "-"))); - if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) { - char *ssl_cert = sl_pem2der(ctx->ssl_cert); - char *ssl_key = sl_pem2der(ctx->ssl_key); - if (ssl_cert != NULL && ssl_key != NULL) { - err = sl_SetSockOpt(sock, SL_SOL_SOCKET, - SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert, - strlen(ssl_cert)); - LOG(LL_INFO, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err)); + if (ctx == NULL) return 0; + DBG(("%p %s,%s,%s,%s", nc, (ctx->ssl_cert ? ctx->ssl_cert : "-"), + (ctx->ssl_key ? ctx->ssl_cert : "-"), + (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : "-"), + (ctx->ssl_server_name ? ctx->ssl_server_name : "-"))); + if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) { + char *ssl_cert = sl_pem2der(ctx->ssl_cert), *ssl_key = NULL; + if (ssl_cert != NULL) { + err = sl_SetSockOpt(sock, SL_SOL_SOCKET, + SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert, + strlen(ssl_cert)); + MG_FREE(ssl_cert); + LOG(LL_DEBUG, ("CERTIFICATE_FILE_NAME %s -> %d", ssl_cert, err)); + ssl_key = sl_pem2der(ctx->ssl_key); + if (ssl_key != NULL) { err = sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key, strlen(ssl_key)); - LOG(LL_INFO, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err)); + MG_FREE(ssl_key); + LOG(LL_DEBUG, ("PRIVATE_KEY_FILE_NAME %s -> %d", ssl_key, err)); } else { err = -1; } - MG_FREE(ssl_cert); - MG_FREE(ssl_key); - if (err != 0) return err; - } - if (ctx->ssl_ca_cert != NULL) { - if (ctx->ssl_ca_cert[0] != '\0') { - char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert); - if (ssl_ca_cert != NULL) { - err = sl_SetSockOpt(sock, SL_SOL_SOCKET, - SL_SO_SECURE_FILES_CA_FILE_NAME, ssl_ca_cert, - strlen(ssl_ca_cert)); - LOG(LL_INFO, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err)); - } else { - err = -1; - } - MG_FREE(ssl_ca_cert); - if (err != 0) return err; + } else { + err = -1; + } + if (err != 0) return err; + } + if (ctx->ssl_ca_cert != NULL) { + if (ctx->ssl_ca_cert[0] != '\0') { + char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert); + if (ssl_ca_cert != NULL) { + err = + sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_CA_FILE_NAME, + ssl_ca_cert, strlen(ssl_ca_cert)); + LOG(LL_DEBUG, ("CA_FILE_NAME %s -> %d", ssl_ca_cert, err)); + } else { + err = -1; } + MG_FREE(ssl_ca_cert); + if (err != 0) return err; } - if (ctx->ssl_server_name != NULL) { - err = sl_SetSockOpt(sock, SL_SOL_SOCKET, - SL_SO_SECURE_DOMAIN_NAME_VERIFICATION, - ctx->ssl_server_name, strlen(ctx->ssl_server_name)); - DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err)); - /* Domain name verificationw as added in a NWP service pack, older - * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do - * about it, - * so we ignore the error. */ - if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err; - } + } + if (ctx->ssl_server_name != NULL) { + err = sl_SetSockOpt(sock, SL_SOL_SOCKET, + SL_SO_SECURE_DOMAIN_NAME_VERIFICATION, + ctx->ssl_server_name, strlen(ctx->ssl_server_name)); + DBG(("DOMAIN_NAME_VERIFICATION %s -> %d", ctx->ssl_server_name, err)); + /* Domain name verificationw as added in a NWP service pack, older + * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do + * about it, + * so we ignore the error. */ + if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err; } return 0; } @@ -14429,10 +14793,6 @@ int sl_set_ssl_opts(int sock, struct mg_connection *nc) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/lwip/mg_lwip_net_if.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_ #define CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_ @@ -14461,9 +14821,9 @@ struct mg_lwip_conn_state { /* Last SSL write size, for retries. */ int last_ssl_write_size; /* Whether MG_SIG_RECV is already pending for this connection */ - int recv_pending : 1; + int recv_pending; /* Whether the connection is about to close, just `rx_chain` needs to drain */ - int draining_rx_chain : 1; + int draining_rx_chain; }; enum mg_sig_type { @@ -14485,10 +14845,6 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr); #ifdef MG_MODULE_LINES #line 1 "common/platforms/lwip/mg_lwip_net_if.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_NET_IF_LWIP_LOW_LEVEL @@ -14499,7 +14855,8 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr); #include #include #if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105 -#include /* For tcp_seg */ +#include /* For tcp_seg */ +#include /* For tcpip_api_call */ #else #include #endif @@ -14539,14 +14896,36 @@ void mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr); #define SET_ADDR(dst, src) (dst)->sin.sin_addr.s_addr = ip_2_ip4(src)->addr #endif -#if NO_SYS -#define tcpip_callback(fn, arg) (fn)(arg) -typedef void (*tcpip_callback_fn)(void *arg); +#if !NO_SYS +#if LWIP_TCPIP_CORE_LOCKING +/* With locking tcpip_api_call is just a function call wrapped in lock/unlock, + * so we can get away with just casting. */ +void mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) { + tcpip_api_call((tcpip_api_call_fn) fn, (struct tcpip_api_call_data *) arg); +} +#else +static sys_sem_t s_tcpip_call_lock_sem = NULL; +static sys_sem_t s_tcpip_call_sync_sem = NULL; +struct mg_lwip_netif_tcpip_call_ctx { + void (*fn)(void *); + void *arg; +}; +static void xxx_tcpip(void *arg) { + struct mg_lwip_netif_tcpip_call_ctx *ctx = + (struct mg_lwip_netif_tcpip_call_ctx *) arg; + ctx->fn(ctx->arg); + sys_sem_signal(&s_tcpip_call_sync_sem); +} +void mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) { + struct mg_lwip_netif_tcpip_call_ctx ctx = {.fn = fn, .arg = arg}; + sys_arch_sem_wait(&s_tcpip_call_lock_sem, 0); + tcpip_send_msg_wait_sem(xxx_tcpip, &ctx, &s_tcpip_call_sync_sem); + sys_sem_signal(&s_tcpip_call_lock_sem); +} +#endif +#else +#define mg_lwip_netif_run_on_tcpip(fn, arg) (fn)(arg) #endif - -void mg_lwip_ssl_do_hs(struct mg_connection *nc); -void mg_lwip_ssl_send(struct mg_connection *nc); -void mg_lwip_ssl_recv(struct mg_connection *nc); void mg_lwip_if_init(struct mg_iface *iface); void mg_lwip_if_free(struct mg_iface *iface); @@ -14554,7 +14933,8 @@ void mg_lwip_if_add_conn(struct mg_connection *nc); void mg_lwip_if_remove_conn(struct mg_connection *nc); time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms); -#if defined(RTOS_SDK) || defined(ESP_PLATFORM) +// If compiling for Mongoose OS. +#ifdef MGOS extern void mgos_lock(); extern void mgos_unlock(); #else @@ -14605,7 +14985,7 @@ static err_t mg_lwip_tcp_conn_cb(void *arg, struct tcp_pcb *tpcb, err_t err) { static void mg_lwip_tcp_error_cb(void *arg, err_t err) { struct mg_connection *nc = (struct mg_connection *) arg; DBG(("%p conn error %d", nc, err)); - if (nc == NULL) return; + if (nc == NULL || (nc->flags & MG_F_CLOSE_IMMEDIATELY)) return; struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; cs->pcb.tcp = NULL; /* Has already been deallocated */ if (nc->flags & MG_F_CONNECTING) { @@ -14619,10 +14999,12 @@ static void mg_lwip_tcp_error_cb(void *arg, err_t err) { static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { struct mg_connection *nc = (struct mg_connection *) arg; - DBG(("%p %p %u %d", nc, tpcb, (p != NULL ? p->tot_len : 0), err)); + struct mg_lwip_conn_state *cs = + (nc ? (struct mg_lwip_conn_state *) nc->sock : NULL); + DBG(("%p %p %p %p %u %d", nc, cs, tpcb, p, (p != NULL ? p->tot_len : 0), + err)); if (p == NULL) { if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; if (cs->rx_chain != NULL) { /* * rx_chain still contains non-consumed data, don't close the @@ -14640,7 +15022,6 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, tcp_abort(tpcb); return ERR_ARG; } - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; /* * If we get a chain of more than one segment at once, we need to bump * refcount on the subsequent bufs to make them independent. @@ -14655,66 +15036,18 @@ static err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, } else if (pbuf_clen(cs->rx_chain) >= 4) { /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX * will be completely blocked. We already have at least 4 in the chain, - * this one is, so we have to make a copy and release this one. */ + * this one is the last, so we have to make a copy and release this one. */ struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM); if (np != NULL) { pbuf_copy(np, p); pbuf_free(p); p = np; - } - } - mgos_unlock(); - mg_lwip_recv_common(nc, p); - return ERR_OK; -} - -static void mg_lwip_consume_rx_chain_tcp(struct mg_connection *nc) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - if (cs->rx_chain == NULL) return; -#if MG_ENABLE_SSL - if (nc->flags & MG_F_SSL) { - if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { - mg_lwip_ssl_recv(nc); - } else { - mg_lwip_ssl_do_hs(nc); - } - return; - } -#endif - mgos_lock(); - while (cs->rx_chain != NULL && nc->recv_mbuf.len < nc->recv_mbuf_limit) { - struct pbuf *seg = cs->rx_chain; - - size_t seg_len = (seg->len - cs->rx_offset); - size_t buf_avail = (nc->recv_mbuf_limit - nc->recv_mbuf.len); - size_t len = MIN(seg_len, buf_avail); - - char *data = (char *) MG_MALLOC(len); - if (data == NULL) { - mgos_unlock(); - DBG(("OOM")); - return; - } - pbuf_copy_partial(seg, data, len, cs->rx_offset); - cs->rx_offset += len; - if (cs->rx_offset == cs->rx_chain->len) { - cs->rx_chain = pbuf_dechain(cs->rx_chain); - pbuf_free(seg); - cs->rx_offset = 0; - } - mgos_unlock(); - mg_if_recv_tcp_cb(nc, data, len, 1 /* own */); - mgos_lock(); + } } + mg_lwip_recv_common(nc, p); mgos_unlock(); -} - -static void mg_lwip_handle_recv_tcp(struct mg_connection *nc) { - mg_lwip_consume_rx_chain_tcp(nc); - - if (nc->send_mbuf.len > 0) { - mg_lwip_mgr_schedule_poll(nc->mgr); - } + (void) err; + return ERR_OK; } static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb, @@ -14726,6 +15059,10 @@ static err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb, nc->send_mbuf.len == 0 && tpcb->unsent == NULL && tpcb->unacked == NULL) { mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); } + if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) { + mg_lwip_mgr_schedule_poll(nc->mgr); + } + (void) num_sent; return ERR_OK; } @@ -14766,7 +15103,7 @@ static void mg_lwip_if_connect_tcp_tcpip(void *arg) { void mg_lwip_if_connect_tcp(struct mg_connection *nc, const union socket_address *sa) { struct mg_lwip_if_connect_tcp_ctx ctx = {.nc = nc, .sa = sa}; - tcpip_callback(mg_lwip_if_connect_tcp_tcpip, &ctx); + mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_tcp_tcpip, &ctx); } /* @@ -14800,13 +15137,14 @@ static void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p, /* Logic in the recv handler requires that there be exactly one data pbuf. */ p = pbuf_coalesce(p, PBUF_RAW); pbuf_chain(sap, p); + mgos_lock(); mg_lwip_recv_common(nc, sap); + mgos_unlock(); (void) pcb; } static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) { struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - mgos_lock(); if (cs->rx_chain == NULL) { cs->rx_chain = p; } else { @@ -14816,32 +15154,30 @@ static void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) { cs->recv_pending = 1; mg_lwip_post_signal(MG_SIG_RECV, nc); } - mgos_unlock(); } -static void mg_lwip_handle_recv_udp(struct mg_connection *nc) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; +static int mg_lwip_if_udp_recv(struct mg_connection *nc, void *buf, size_t len, + union socket_address *sa, size_t *sa_len) { /* * For UDP, RX chain consists of interleaved address and packet bufs: * Address pbuf followed by exactly one data pbuf (recv_cb took care of that). */ - while (cs->rx_chain != NULL) { - struct pbuf *sap = cs->rx_chain; - struct pbuf *p = sap->next; - cs->rx_chain = pbuf_dechain(p); - size_t data_len = p->len; - char *data = (char *) MG_MALLOC(data_len); - if (data != NULL) { - pbuf_copy_partial(p, data, data_len, 0); - pbuf_free(p); - mg_if_recv_udp_cb(nc, data, data_len, - (union socket_address *) sap->payload, sap->len); - pbuf_free(sap); - } else { - pbuf_free(p); - pbuf_free(sap); - } + int res = 0; + struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; + if (nc->sock == INVALID_SOCKET) return -1; + mgos_lock(); + if (cs->rx_chain != NULL) { + struct pbuf *ap = cs->rx_chain; + struct pbuf *dp = ap->next; + cs->rx_chain = pbuf_dechain(dp); + res = MIN(dp->len, len); + pbuf_copy_partial(dp, buf, res, 0); + pbuf_free(dp); + pbuf_copy_partial(ap, sa, MIN(*sa_len, ap->len), 0); + pbuf_free(ap); } + mgos_unlock(); + return res; } static void mg_lwip_if_connect_udp_tcpip(void *arg) { @@ -14860,14 +15196,7 @@ static void mg_lwip_if_connect_udp_tcpip(void *arg) { } void mg_lwip_if_connect_udp(struct mg_connection *nc) { - tcpip_callback(mg_lwip_if_connect_udp_tcpip, nc); -} - -void mg_lwip_accept_conn(struct mg_connection *nc, struct tcp_pcb *tpcb) { - union socket_address sa; - SET_ADDR(&sa, &tpcb->remote_ip); - sa.sin.sin_port = htons(tpcb->remote_port); - mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin)); + mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_udp_tcpip, nc); } static void tcp_close_tcpip(void *arg) { @@ -14877,17 +15206,11 @@ static void tcp_close_tcpip(void *arg) { void mg_lwip_handle_accept(struct mg_connection *nc) { struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; if (cs->pcb.tcp == NULL) return; -#if MG_ENABLE_SSL - if (cs->lc->flags & MG_F_SSL) { - if (mg_ssl_if_conn_accept(nc, cs->lc) != MG_SSL_OK) { - LOG(LL_ERROR, ("SSL error")); - tcpip_callback(tcp_close_tcpip, cs->pcb.tcp); - } - } else -#endif - { - mg_lwip_accept_conn(nc, cs->pcb.tcp); - } + union socket_address sa; + struct tcp_pcb *tpcb = cs->pcb.tcp; + SET_ADDR(&sa, &tpcb->remote_ip); + sa.sin.sin_port = htons(tpcb->remote_port); + mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin)); } static err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) { @@ -14959,7 +15282,7 @@ static void mg_lwip_if_listen_tcp_tcpip(void *arg) { int mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) { struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa}; - tcpip_callback(mg_lwip_if_listen_tcp_tcpip, &ctx); + mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_tcp_tcpip, &ctx); return ctx.ret; } @@ -14985,7 +15308,7 @@ static void mg_lwip_if_listen_udp_tcpip(void *arg) { int mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) { struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa}; - tcpip_callback(mg_lwip_if_listen_udp_tcpip, &ctx); + mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_udp_tcpip, &ctx); return ctx.ret; } @@ -15010,7 +15333,7 @@ static void mg_lwip_tcp_write_tcpip(void *arg) { if (len == 0) { DBG(("%p no buf avail %u %u %p %p", tpcb, tpcb->snd_buf, tpcb->snd_queuelen, tpcb->unsent, tpcb->unacked)); - tcpip_callback(tcp_output_tcpip, tpcb); + mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, tpcb); ctx->ret = 0; return; } @@ -15042,17 +15365,18 @@ static void mg_lwip_tcp_write_tcpip(void *arg) { return; } ctx->ret = len; + (void) unsent; + (void) unacked; } -static int mg_lwip_tcp_write(struct mg_connection *nc, const void *data, - uint16_t len) { - struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = data, .len = len}; +int mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) { + struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = buf, .len = len}; struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; + if (nc->sock == INVALID_SOCKET) return -1; struct tcp_pcb *tpcb = cs->pcb.tcp; - if (tpcb == NULL) { - return -1; - } - tcpip_callback(mg_lwip_tcp_write_tcpip, &ctx); + if (tpcb == NULL) return -1; + if (tpcb->snd_buf <= 0) return 0; + mg_lwip_netif_run_on_tcpip(mg_lwip_tcp_write_tcpip, &ctx); return ctx.ret; } @@ -15069,18 +15393,10 @@ static void udp_sendto_tcpip(void *arg) { ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port); } -static int mg_lwip_udp_send(struct mg_connection *nc, const void *data, - uint16_t len) { +static int mg_lwip_if_udp_send(struct mg_connection *nc, const void *data, + size_t len) { struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - if (cs->pcb.udp == NULL) { - /* - * In case of UDP, this usually means, what - * async DNS resolve is still in progress and connection - * is not ready yet - */ - DBG(("%p socket is not connected", nc)); - return -1; - } + if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) return -1; struct udp_pcb *upcb = cs->pcb.udp; struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); #if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6 @@ -15089,46 +15405,32 @@ static int mg_lwip_udp_send(struct mg_connection *nc, const void *data, ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr}; #endif u16_t port = ntohs(nc->sa.sin.sin_port); - if (p == NULL) { - DBG(("OOM")); - return 0; - } + if (p == NULL) return 0; memcpy(p->payload, data, len); struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port}; - tcpip_callback(udp_sendto_tcpip, &ctx); + mg_lwip_netif_run_on_tcpip(udp_sendto_tcpip, &ctx); cs->err = ctx.ret; pbuf_free(p); - return (cs->err == ERR_OK ? len : -1); + return (cs->err == ERR_OK ? (int) len : -2); } -static void mg_lwip_send_more(struct mg_connection *nc) { - int num_sent = 0; - if (nc->sock == INVALID_SOCKET) return; - if (nc->flags & MG_F_UDP) { - num_sent = mg_lwip_udp_send(nc, nc->send_mbuf.buf, nc->send_mbuf.len); - DBG(("%p mg_lwip_udp_send %u = %d", nc, nc->send_mbuf.len, num_sent)); - } else { - num_sent = mg_lwip_tcp_write(nc, nc->send_mbuf.buf, nc->send_mbuf.len); - DBG(("%p mg_lwip_tcp_write %u = %d", nc, nc->send_mbuf.len, num_sent)); - } - if (num_sent == 0) return; - if (num_sent > 0) { - mg_if_sent_cb(nc, num_sent); - } else { - mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); +static int mg_lwip_if_can_send(struct mg_connection *nc, + struct mg_lwip_conn_state *cs) { + int can_send = 0; + if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) { + /* We have stuff to send, but can we? */ + if (nc->flags & MG_F_UDP) { + /* UDP is always ready for sending. */ + can_send = (cs->pcb.udp != NULL); + } else { + can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0); +/* See comment above. */ +#if CS_PLATFORM == CS_P_ESP8266 + if (cs->pcb.tcp->unacked != NULL) can_send = 0; +#endif + } } -} - -void mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, - size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); - mg_lwip_mgr_schedule_poll(nc->mgr); -} - -void mg_lwip_if_udp_send(struct mg_connection *nc, const void *buf, - size_t len) { - mbuf_append(&nc->send_mbuf, buf, len); - mg_lwip_mgr_schedule_poll(nc->mgr); + return can_send; } struct tcp_recved_ctx { @@ -15138,30 +15440,38 @@ 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); } -void mg_lwip_if_recved(struct mg_connection *nc, size_t len) { - if (nc->flags & MG_F_UDP) return; +static int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf, + size_t len) { + int res = 0; + char *bufp = buf; struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - if (nc->sock == INVALID_SOCKET || cs->pcb.tcp == NULL) { - DBG(("%p invalid socket", nc)); - return; + if (nc->sock == INVALID_SOCKET) return -1; + mgos_lock(); + while (cs->rx_chain != NULL && len > 0) { + struct pbuf *seg = cs->rx_chain; + size_t seg_len = (seg->len - cs->rx_offset); + size_t copy_len = MIN(len, seg_len); + + pbuf_copy_partial(seg, bufp, copy_len, cs->rx_offset); + len -= copy_len; + res += copy_len; + bufp += copy_len; + cs->rx_offset += copy_len; + if (cs->rx_offset == cs->rx_chain->len) { + cs->rx_chain = pbuf_dechain(cs->rx_chain); + pbuf_free(seg); + cs->rx_offset = 0; + } } - DBG(("%p %p %u %u", nc, cs->pcb.tcp, len, - (cs->rx_chain ? cs->rx_chain->tot_len : 0))); - struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = len}; -#if MG_ENABLE_SSL - if (!(nc->flags & MG_F_SSL)) { - tcpip_callback(tcp_recved_tcpip, &ctx); - } else { - /* Currently SSL acknowledges data immediately. - * TODO(rojer): Find a way to propagate mg_lwip_if_recved. */ + mgos_unlock(); + if (res > 0) { + struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = res}; + mg_lwip_netif_run_on_tcpip(tcp_recved_tcpip, &ctx); } -#else - tcpip_callback(tcp_recved_tcpip, &ctx); -#endif - mbuf_trim(&nc->recv_mbuf); + return res; } int mg_lwip_if_create_conn(struct mg_connection *nc) { @@ -15186,7 +15496,7 @@ void mg_lwip_if_destroy_conn(struct mg_connection *nc) { tcp_arg(tpcb, NULL); DBG(("%p tcp_close %p", nc, tpcb)); tcp_arg(tpcb, NULL); - tcpip_callback(tcp_close_tcpip, tpcb); + mg_lwip_netif_run_on_tcpip(tcp_close_tcpip, tpcb); } while (cs->rx_chain != NULL) { struct pbuf *seg = cs->rx_chain; @@ -15200,7 +15510,7 @@ void mg_lwip_if_destroy_conn(struct mg_connection *nc) { struct udp_pcb *upcb = cs->pcb.udp; if (upcb != NULL) { DBG(("%p udp_remove %p", nc, upcb)); - tcpip_callback(udp_remove_tcpip, upcb); + mg_lwip_netif_run_on_tcpip(udp_remove_tcpip, upcb); } memset(cs, 0, sizeof(*cs)); MG_FREE(cs); @@ -15250,7 +15560,8 @@ void mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) { mg_lwip_if_connect_udp, \ mg_lwip_if_tcp_send, \ mg_lwip_if_udp_send, \ - mg_lwip_if_recved, \ + mg_lwip_if_tcp_recv, \ + mg_lwip_if_udp_recv, \ mg_lwip_if_create_conn, \ mg_lwip_if_destroy_conn, \ mg_lwip_if_sock_set, \ @@ -15267,10 +15578,6 @@ const struct mg_iface_vtable mg_default_iface_vtable = MG_LWIP_IFACE_VTABLE; #ifdef MG_MODULE_LINES #line 1 "common/platforms/lwip/mg_lwip_ev_mgr.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL @@ -15310,38 +15617,27 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) { (struct mg_ev_mgr_lwip_data *) mgr->ifaces[MG_MAIN_IFACE]->data; while (md->sig_queue_len > 0) { mgos_lock(); - int sig = md->sig_queue[md->start_index].sig; - struct mg_connection *nc = md->sig_queue[md->start_index].nc; + int i = md->start_index; + int sig = md->sig_queue[i].sig; + struct mg_connection *nc = md->sig_queue[i].nc; struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - md->start_index = (md->start_index + 1) % MG_SIG_QUEUE_LEN; + md->start_index = (i + 1) % MG_SIG_QUEUE_LEN; md->sig_queue_len--; mgos_unlock(); if (nc->iface == NULL || nc->mgr == NULL) continue; switch (sig) { case MG_SIG_CONNECT_RESULT: { -#if MG_ENABLE_SSL - if (cs->err == 0 && (nc->flags & MG_F_SSL) && - !(nc->flags & MG_F_SSL_HANDSHAKE_DONE)) { - mg_lwip_ssl_do_hs(nc); - } else -#endif - { - mg_if_connect_cb(nc, cs->err); - } + mg_if_connect_cb(nc, cs->err); break; } case MG_SIG_CLOSE_CONN: { - nc->flags |= MG_F_SEND_AND_CLOSE; mg_close_conn(nc); break; } case MG_SIG_RECV: { cs->recv_pending = 0; - if (nc->flags & MG_F_UDP) { - mg_lwip_handle_recv_udp(nc); - } else { - mg_lwip_handle_recv_tcp(nc); - } + mg_if_can_recv_cb(nc); + mbuf_trim(&nc->recv_mbuf); break; } case MG_SIG_TOMBSTONE: { @@ -15356,8 +15652,13 @@ void mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) { } void mg_lwip_if_init(struct mg_iface *iface) { - LOG(LL_INFO, ("%p Mongoose init", iface)); + LOG(LL_INFO, ("Mongoose %s, LwIP %u.%u.%u", MG_VERSION, LWIP_VERSION_MAJOR, + LWIP_VERSION_MINOR, LWIP_VERSION_REVISION)); iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data)); +#if !NO_SYS && !LWIP_TCPIP_CORE_LOCKING + sys_sem_new(&s_tcpip_call_lock_sem, 1); + sys_sem_new(&s_tcpip_call_sync_sem, 0); +#endif } void mg_lwip_if_free(struct mg_iface *iface) { @@ -15395,46 +15696,11 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) { struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; tmp = nc->next; n++; - if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) || - ((nc->flags & MG_F_SEND_AND_CLOSE) && (nc->flags & MG_F_UDP) && - (nc->send_mbuf.len == 0))) { - mg_close_conn(nc); - continue; - } - mg_if_poll(nc, now); - mg_if_timer(nc, now); -#if MG_ENABLE_SSL - if ((nc->flags & MG_F_SSL) && cs != NULL && cs->pcb.tcp != NULL && - cs->pcb.tcp->state == ESTABLISHED) { - if (((nc->flags & MG_F_WANT_WRITE) || - ((nc->send_mbuf.len > 0) && - (nc->flags & MG_F_SSL_HANDSHAKE_DONE))) && - cs->pcb.tcp->snd_buf > 0) { - /* Can write more. */ - if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { - if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_send(nc); - } else { - mg_lwip_ssl_do_hs(nc); - } - } - if (cs->rx_chain != NULL || (nc->flags & MG_F_WANT_READ)) { - if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) { - if (!(nc->flags & MG_F_CONNECTING)) mg_lwip_ssl_recv(nc); - } else { - mg_lwip_ssl_do_hs(nc); - } - } - } else -#endif /* MG_ENABLE_SSL */ - { - if (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING)) { - mg_lwip_send_more(nc); - } - } + if (!mg_if_poll(nc, now)) continue; if (nc->sock != INVALID_SOCKET && !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL && cs->pcb.tcp->unsent != NULL) { - tcpip_callback(tcp_output_tcpip, cs->pcb.tcp); + mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, cs->pcb.tcp); } if (nc->ev_timer_time > 0) { if (num_timers == 0 || nc->ev_timer_time < min_timer) { @@ -15444,14 +15710,17 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) { } if (nc->sock != INVALID_SOCKET) { - /* Try to consume data from cs->rx_chain */ - mg_lwip_consume_rx_chain_tcp(nc); - - /* - * If the connection is about to close, and rx_chain is finally empty, - * send the MG_SIG_CLOSE_CONN signal - */ - if (cs->draining_rx_chain && cs->rx_chain == NULL) { + if (mg_lwip_if_can_send(nc, cs)) { + mg_if_can_send_cb(nc); + mbuf_trim(&nc->send_mbuf); + } + if (cs->rx_chain != NULL) { + mg_if_can_recv_cb(nc); + } else if (cs->draining_rx_chain) { + /* + * If the connection is about to close, and rx_chain is finally empty, + * send the MG_SIG_CLOSE_CONN signal + */ mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); } } @@ -15465,258 +15734,10 @@ time_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) { return now; } -uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr) { - struct mg_connection *nc; - double now; - double min_timer = 0; - int num_timers = 0; - mg_ev_mgr_lwip_process_signals(mgr); - for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - if (nc->ev_timer_time > 0) { - if (num_timers == 0 || nc->ev_timer_time < min_timer) { - min_timer = nc->ev_timer_time; - } - num_timers++; - } - if (nc->send_mbuf.len > 0 -#if MG_ENABLE_SSL - || (nc->flags & MG_F_WANT_WRITE) -#endif - ) { - int can_send = 0; - /* We have stuff to send, but can we? */ - if (nc->flags & MG_F_UDP) { - /* UDP is always ready for sending. */ - can_send = (cs->pcb.udp != NULL); - } else { - can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0); - } - /* We want and can send, request a poll immediately. */ - if (can_send) return 0; - } - } - uint32_t timeout_ms = ~0; - now = mg_time(); - if (num_timers > 0) { - /* If we have a timer that is past due, do a poll ASAP. */ - if (min_timer < now) return 0; - double timer_timeout_ms = (min_timer - now) * 1000 + 1 /* rounding */; - if (timer_timeout_ms < timeout_ms) { - timeout_ms = timer_timeout_ms; - } - } - return timeout_ms; -} - #endif /* MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */ #ifdef MG_MODULE_LINES -#line 1 "common/platforms/lwip/mg_lwip_ssl_if.c" -#endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ - -#if MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL - -/* Amalgamated: #include "common/mg_mem.h" */ -/* Amalgamated: #include "common/cs_dbg.h" */ - -#include -#include - -#ifndef MG_LWIP_SSL_IO_SIZE -#define MG_LWIP_SSL_IO_SIZE 1024 -#endif - -#ifndef MIN -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#endif - -void mg_lwip_ssl_do_hs(struct mg_connection *nc) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - int server_side = (nc->listener != NULL); - enum mg_ssl_if_result res; - if (nc->flags & MG_F_CLOSE_IMMEDIATELY) return; - res = mg_ssl_if_handshake(nc); - DBG(("%p %lu %d %d", nc, nc->flags, server_side, res)); - if (res != MG_SSL_OK) { - if (res == MG_SSL_WANT_WRITE) { - nc->flags |= MG_F_WANT_WRITE; - cs->err = 0; - } else if (res == MG_SSL_WANT_READ) { - /* - * Nothing to do in particular, we are callback-driven. - * What we definitely do not need anymore is SSL reading (nothing left). - */ - nc->flags &= ~MG_F_WANT_READ; - cs->err = 0; - } else { - cs->err = res; - if (server_side) { - mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); - } else { - mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc); - } - } - } else { - cs->err = 0; - nc->flags &= ~MG_F_WANT_WRITE; - /* - * Handshake is done. Schedule a read immediately to consume app data - * which may already be waiting. - */ - nc->flags |= (MG_F_SSL_HANDSHAKE_DONE | MG_F_WANT_READ); - if (server_side) { - mg_lwip_accept_conn(nc, cs->pcb.tcp); - } else { - mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc); - } - } -} - -void mg_lwip_ssl_send(struct mg_connection *nc) { - if (nc->sock == INVALID_SOCKET) { - DBG(("%p invalid socket", nc)); - return; - } - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - /* It's ok if the buffer is empty. Return value of 0 may also be valid. */ - int len = cs->last_ssl_write_size; - if (len == 0) { - len = MIN(MG_LWIP_SSL_IO_SIZE, nc->send_mbuf.len); - } - int ret = mg_ssl_if_write(nc, nc->send_mbuf.buf, len); - DBG(("%p SSL_write %u = %d", nc, len, ret)); - if (ret > 0) { - mg_if_sent_cb(nc, ret); - cs->last_ssl_write_size = 0; - } else if (ret < 0) { - /* This is tricky. We must remember the exact data we were sending to retry - * exactly the same send next time. */ - cs->last_ssl_write_size = len; - } - if (ret == len) { - nc->flags &= ~MG_F_WANT_WRITE; - } else if (ret == MG_SSL_WANT_WRITE) { - nc->flags |= MG_F_WANT_WRITE; - } else { - mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); - } -} - -void mg_lwip_ssl_recv(struct mg_connection *nc) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - /* Don't deliver data before connect callback */ - if (nc->flags & MG_F_CONNECTING) return; - while (nc->recv_mbuf.len < nc->recv_mbuf_limit) { - char *buf = (char *) MG_MALLOC(MG_LWIP_SSL_IO_SIZE); - if (buf == NULL) return; - int ret = mg_ssl_if_read(nc, buf, MG_LWIP_SSL_IO_SIZE); - DBG(("%p %p SSL_read %u = %d", nc, cs->rx_chain, MG_LWIP_SSL_IO_SIZE, ret)); - if (ret <= 0) { - MG_FREE(buf); - if (ret == MG_SSL_WANT_WRITE) { - nc->flags |= MG_F_WANT_WRITE; - return; - } else if (ret == MG_SSL_WANT_READ) { - /* - * Nothing to do in particular, we are callback-driven. - * What we definitely do not need anymore is SSL reading (nothing left). - */ - nc->flags &= ~MG_F_WANT_READ; - cs->err = 0; - return; - } else { - mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc); - return; - } - } else { - mg_if_recv_tcp_cb(nc, buf, ret, 1 /* own */); - } - } -} - -#ifdef KR_VERSION - -ssize_t kr_send(int fd, const void *buf, size_t len) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd; - int ret = mg_lwip_tcp_write(cs->nc, buf, len); - DBG(("%p mg_lwip_tcp_write %u = %d", cs->nc, len, ret)); - if (ret == 0) ret = KR_IO_WOULDBLOCK; - return ret; -} - -ssize_t kr_recv(int fd, void *buf, size_t len) { - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) fd; - struct pbuf *seg = cs->rx_chain; - if (seg == NULL) { - DBG(("%u - nothing to read", len)); - return KR_IO_WOULDBLOCK; - } - size_t seg_len = (seg->len - cs->rx_offset); - DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len)); - len = MIN(len, seg_len); - pbuf_copy_partial(seg, buf, len, cs->rx_offset); - cs->rx_offset += len; - tcp_recved(cs->pcb.tcp, len); - if (cs->rx_offset == cs->rx_chain->len) { - cs->rx_chain = pbuf_dechain(cs->rx_chain); - pbuf_free(seg); - cs->rx_offset = 0; - } - return len; -} - -#elif MG_SSL_IF == MG_SSL_IF_MBEDTLS - -int ssl_socket_send(void *ctx, const unsigned char *buf, size_t len) { - struct mg_connection *nc = (struct mg_connection *) ctx; - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - int ret = mg_lwip_tcp_write(cs->nc, buf, len); - if (ret == 0) ret = MBEDTLS_ERR_SSL_WANT_WRITE; - LOG(LL_DEBUG, ("%p %d -> %d", nc, len, ret)); - return ret; -} - -int ssl_socket_recv(void *ctx, unsigned char *buf, size_t len) { - struct mg_connection *nc = (struct mg_connection *) ctx; - struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock; - struct pbuf *seg = cs->rx_chain; - if (seg == NULL) { - DBG(("%u - nothing to read", len)); - return MBEDTLS_ERR_SSL_WANT_READ; - } - size_t seg_len = (seg->len - cs->rx_offset); - DBG(("%u %u %u %u", len, cs->rx_chain->len, seg_len, cs->rx_chain->tot_len)); - mgos_lock(); - len = MIN(len, seg_len); - pbuf_copy_partial(seg, buf, len, cs->rx_offset); - cs->rx_offset += len; - /* TCP PCB may be NULL if connection has already been closed - * but we still have data to deliver to SSL. */ - if (cs->pcb.tcp != NULL) tcp_recved(cs->pcb.tcp, len); - if (cs->rx_offset == cs->rx_chain->len) { - cs->rx_chain = pbuf_dechain(cs->rx_chain); - pbuf_free(seg); - cs->rx_offset = 0; - } - mgos_unlock(); - LOG(LL_DEBUG, ("%p <- %d", nc, (int) len)); - return len; -} - -#endif - -#endif /* MG_ENABLE_SSL && MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */ -#ifdef MG_MODULE_LINES #line 1 "common/platforms/wince/wince_libc.c" #endif -/* - * Copyright (c) 2016 Cesanta Software Limited - * All rights reserved - */ #ifdef WINCE @@ -15790,10 +15811,6 @@ static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) { #ifdef MG_MODULE_LINES #line 1 "common/platforms/pic32/pic32_net_if.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PIC32_NET_IF_H_ #define CS_COMMON_PLATFORMS_PIC32_NET_IF_H_ @@ -15818,10 +15835,6 @@ extern const struct mg_iface_vtable mg_pic32_iface_vtable; #ifdef MG_MODULE_LINES #line 1 "common/platforms/pic32/pic32_net_if.c" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #if MG_ENABLE_NET_IF_PIC32 @@ -16113,10 +16126,6 @@ const struct mg_iface_vtable mg_default_iface_vtable = MG_PIC32_IFACE_VTABLE; #ifdef MG_MODULE_LINES #line 1 "common/platforms/windows/windows_direct.c" #endif -/* - * Copyright (c) 2017 Cesanta Software Limited - * All rights reserved - */ #ifdef _WIN32 diff --git a/mongoose.h b/mongoose.h index eecbbef..3d5d2cb 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1,9 +1,6 @@ -#ifdef MG_MODULE_LINES -#line 1 "mongoose/src/mg_common.h" -#endif /* * Copyright (c) 2004-2013 Sergey Lyubka - * Copyright (c) 2013-2015 Cesanta Software Limited + * Copyright (c) 2013-2020 Cesanta Software Limited * All rights reserved * * This software is dual-licensed: you can redistribute it and/or modify @@ -20,10 +17,13 @@ * license, as set out in . */ +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/mg_common.h" +#endif #ifndef CS_MONGOOSE_SRC_COMMON_H_ #define CS_MONGOOSE_SRC_COMMON_H_ -#define MG_VERSION "6.11" +#define MG_VERSION "6.18" /* Local tweaks, applied before any of Mongoose's own headers. */ #ifdef MG_LOCALS @@ -58,8 +58,9 @@ #define CS_P_NRF51 12 #define CS_P_NRF52 10 #define CS_P_PIC32 11 +#define CS_P_RS14100 18 #define CS_P_STM32 16 -/* Next id: 18 */ +/* Next id: 19 */ /* If not specified explicitly, we guess platform by defines. */ #ifndef CS_PLATFORM @@ -91,6 +92,8 @@ #elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \ defined(TARGET_IS_TM4C129_RA2) #define CS_PLATFORM CS_P_TM4C129 +#elif defined(RS14100) +#define CS_PLATFORM CS_P_RS14100 #elif defined(STM32) #define CS_PLATFORM CS_P_STM32 #endif @@ -105,6 +108,7 @@ #define MG_NET_IF_SIMPLELINK 2 #define MG_NET_IF_LWIP_LOW_LEVEL 3 #define MG_NET_IF_PIC32 4 +#define MG_NET_IF_NULL 5 #define MG_SSL_IF_OPENSSL 1 #define MG_SSL_IF_MBEDTLS 2 @@ -124,12 +128,26 @@ /* Amalgamated: #include "common/platforms/platform_nxp_lpc.h" */ /* Amalgamated: #include "common/platforms/platform_nxp_kinetis.h" */ /* Amalgamated: #include "common/platforms/platform_pic32.h" */ +/* Amalgamated: #include "common/platforms/platform_rs14100.h" */ /* Amalgamated: #include "common/platforms/platform_stm32.h" */ +#if CS_PLATFORM == CS_P_CUSTOM +#include +#endif /* Common stuff */ +#if !defined(PRINTF_LIKE) +#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) +#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a))) +#else +#define PRINTF_LIKE(f, a) +#endif +#endif + #if !defined(WEAK) -#if (defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__TI_COMPILER_VERSION__)) && \ + !defined(_WIN32) #define WEAK __attribute__((weak)) #else #define WEAK @@ -210,7 +228,7 @@ #include #include -#if _MSC_VER < 1700 +#if defined(_MSC_VER) && (_MSC_VER < 1700) typedef int bool; #else #include @@ -492,10 +510,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_esp32.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ #define CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ @@ -537,10 +551,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_esp8266.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ #define CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ @@ -569,10 +579,6 @@ typedef struct stat cs_stat_t; #define __cdecl #define _FILE_OFFSET_BITS 32 -#if !defined(RTOS_SDK) && !defined(__cplusplus) -#define fileno(x) -1 -#endif - #define MG_LWIP 1 /* struct timeval is defined in sys/time.h. */ @@ -602,10 +608,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_cc3100.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_ #define CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_ @@ -651,10 +653,6 @@ int inet_pton(int af, const char *src, void *dst); #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_cc3200.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ #define CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ @@ -774,12 +772,116 @@ int stat(const char *pathname, struct stat *st); #endif /* CS_PLATFORM == CS_P_CC3200 */ #endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */ #ifdef MG_MODULE_LINES +#line 1 "common/platforms/platform_cc3220.h" +#endif + +#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_ +#define CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_ +#if CS_PLATFORM == CS_P_CC3220 + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __TI_COMPILER_VERSION__ +#include +#include +#endif + +#define MG_NET_IF MG_NET_IF_SIMPLELINK +#ifndef MG_SSL_IF +#define MG_SSL_IF MG_SSL_IF_SIMPLELINK +#endif + +/* Only SPIFFS supports directories, SLFS does not. */ +#if defined(CC3220_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING) +#define MG_ENABLE_DIRECTORY_LISTING 1 +#endif + +/* Amalgamated: #include "common/platforms/simplelink/cs_simplelink.h" */ + +typedef int sock_t; +#define INVALID_SOCKET (-1) +#define SIZE_T_FMT "u" +typedef struct stat cs_stat_t; +#define DIRSEP '/' +#define to64(x) strtoll(x, NULL, 10) +#define INT64_FMT PRId64 +#define INT64_X_FMT PRIx64 +#define __cdecl + +#define fileno(x) -1 + +/* Some functions we implement for Mongoose. */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __TI_COMPILER_VERSION__ +struct SlTimeval_t; +#define timeval SlTimeval_t +int gettimeofday(struct timeval *t, void *tz); +int settimeofday(const struct timeval *tv, const void *tz); + +int asprintf(char **strp, const char *fmt, ...); + +#endif + +/* TI's libc does not have stat & friends, add them. */ +#ifdef __TI_COMPILER_VERSION__ + +#include + +typedef unsigned int mode_t; +typedef size_t _off_t; +typedef long ssize_t; + +struct stat { + int st_ino; + mode_t st_mode; + int st_nlink; + time_t st_mtime; + off_t st_size; +}; + +int _stat(const char *pathname, struct stat *st); +int stat(const char *pathname, struct stat *st); + +#define __S_IFMT 0170000 + +#define __S_IFDIR 0040000 +#define __S_IFCHR 0020000 +#define __S_IFREG 0100000 + +#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask)) + +#define S_IFDIR __S_IFDIR +#define S_IFCHR __S_IFCHR +#define S_IFREG __S_IFREG +#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR) +#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG) + +#endif /* __TI_COMPILER_VERSION__ */ + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CS_PLATFORM == CS_P_CC3220 */ +#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */ +#ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_msp432.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_ #define CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_ @@ -884,10 +986,6 @@ int _stat(const char *pathname, struct stat *st); #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_tm4c129.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_ #define CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_ @@ -944,10 +1042,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_mbed.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_MBED_H_ #define CS_COMMON_PLATFORMS_PLATFORM_MBED_H_ @@ -1028,10 +1122,6 @@ in_addr_t inet_addr(const char *cp); #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_nrf51.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ #define CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ #if CS_PLATFORM == CS_P_NRF51 @@ -1073,10 +1163,6 @@ int gettimeofday(struct timeval *tp, void *tzp); #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_nrf52.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ #define CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ #if CS_PLATFORM == CS_P_NRF52 @@ -1121,10 +1207,6 @@ int gettimeofday(struct timeval *tp, void *tzp); #ifdef MG_MODULE_LINES #line 1 "common/platforms/simplelink/cs_simplelink.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ #define CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ @@ -1280,7 +1362,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 */ @@ -1492,10 +1574,6 @@ const char *strerror(); #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_nxp_lpc.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ #define CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ @@ -1548,10 +1626,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_nxp_kinetis.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ #define CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ @@ -1582,10 +1656,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_pic32.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ #define CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ @@ -1620,12 +1690,56 @@ char *inet_ntoa(struct in_addr in); #endif /* CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ */ #ifdef MG_MODULE_LINES +#line 1 "common/platforms/platform_rs14100.h" +#endif + +#ifndef CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_ +#define CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_ +#if CS_PLATFORM == CS_P_RS14100 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef MGOS_HAVE_VFS_COMMON +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define to64(x) strtoll(x, NULL, 10) +#define INT64_FMT "lld" +#define SIZE_T_FMT "u" +typedef struct stat cs_stat_t; +#define DIRSEP '/' + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 1 +#endif + +#ifndef MG_ENABLE_FILESYSTEM +#define MG_ENABLE_FILESYSTEM 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CS_PLATFORM == CS_P_RS14100 */ +#endif /* CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_ */ +#ifdef MG_MODULE_LINES #line 1 "common/platforms/platform_stm32.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ #define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ @@ -1646,7 +1760,7 @@ char *inet_ntoa(struct in_addr in); #include #define to64(x) strtoll(x, NULL, 10) -#define INT64_FMT PRId64 +#define INT64_FMT "lld" #define SIZE_T_FMT "u" typedef struct stat cs_stat_t; #define DIRSEP '/' @@ -1664,10 +1778,6 @@ typedef struct stat cs_stat_t; #ifdef MG_MODULE_LINES #line 1 "common/platforms/lwip/mg_lwip.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_ #define CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_ @@ -1720,7 +1830,6 @@ typedef int sock_t; #if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL struct mg_mgr; struct mg_connection; -uint32_t mg_lwip_get_poll_delay_ms(struct mg_mgr *mgr); void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle, int interval, int count); #endif @@ -1736,10 +1845,6 @@ void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle, #ifdef MG_MODULE_LINES #line 1 "common/cs_md5.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_MD5_H_ #define CS_COMMON_MD5_H_ @@ -1772,10 +1877,6 @@ void cs_md5_final(unsigned char *md, cs_md5_ctx *c); #ifdef MG_MODULE_LINES #line 1 "common/cs_sha1.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_SHA1_H_ #define CS_COMMON_SHA1_H_ @@ -1814,10 +1915,6 @@ void cs_hmac_sha1(const unsigned char *key, size_t key_len, #ifdef MG_MODULE_LINES #line 1 "common/cs_time.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_CS_TIME_H_ #define CS_COMMON_CS_TIME_H_ @@ -1847,18 +1944,12 @@ double cs_timegm(const struct tm *tm); #ifdef MG_MODULE_LINES #line 1 "common/mg_str.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_MG_STR_H_ #define CS_COMMON_MG_STR_H_ #include -/* Amalgamated: #include "common/platform.h" */ - #ifdef __cplusplus extern "C" { #endif @@ -1883,6 +1974,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 } @@ -1922,11 +2015,27 @@ 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); +/* + * Compare two `mg_str`s ignoreing case; return value is the same as `strcmp`. + */ +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2); + +/* + * 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`. */ 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 @@ -1935,14 +2044,8 @@ const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); #ifdef MG_MODULE_LINES #line 1 "common/mbuf.h" #endif -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - */ /* - * === Memory Buffers - * * Mbufs are mutable/growing memory buffers, like C++ strings. * Mbuf can append data to the end of a buffer or insert data into arbitrary * position in the middle of a buffer. The buffer grows automatically when @@ -1963,6 +2066,14 @@ extern "C" { #define MBUF_SIZE_MULTIPLIER 1.5 #endif +#ifndef MBUF_SIZE_MAX_HEADROOM +#ifdef BUFSIZ +#define MBUF_SIZE_MAX_HEADROOM BUFSIZ +#else +#define MBUF_SIZE_MAX_HEADROOM 1024 +#endif +#endif + /* Memory buffer descriptor */ struct mbuf { char *buf; /* Buffer pointer */ @@ -1986,6 +2097,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. * @@ -2006,6 +2125,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 *); @@ -2017,10 +2142,6 @@ void mbuf_trim(struct mbuf *); #ifdef MG_MODULE_LINES #line 1 "common/cs_base64.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_CS_BASE64_H_ #define CS_COMMON_CS_BASE64_H_ @@ -2054,6 +2175,14 @@ void cs_base64_finish(struct cs_base64_ctx *ctx); void cs_base64_encode(const unsigned char *src, int src_len, char *dst); void cs_fprint_base64(FILE *f, const unsigned char *src, int src_len); + +/* + * Decodes a base64 string `s` length `len` into `dst`. + * `dst` must have enough space to hold the result. + * `*dec_len` will contain the resulting length of the string in `dst` + * while return value will return number of processed bytes in `src`. + * Return value == len indicates successful processing of all the data. + */ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len); #ifdef __cplusplus @@ -2066,10 +2195,6 @@ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len); #ifdef MG_MODULE_LINES #line 1 "common/str_util.h" #endif -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_COMMON_STR_UTIL_H_ #define CS_COMMON_STR_UTIL_H_ @@ -2092,7 +2217,11 @@ int cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len); * Expands to a string representation of its argument: e.g. * `CS_STRINGIFY_LIT(5) expands to "5"` */ +#if !defined(_MSC_VER) || _MSC_VER >= 1900 +#define CS_STRINGIFY_LIT(...) #__VA_ARGS__ +#else #define CS_STRINGIFY_LIT(x) #x +#endif /* * Expands to a string representation of its argument, which is allowed @@ -2117,7 +2246,8 @@ size_t c_strnlen(const char *s, size_t maxlen); /* * Equivalent of standard `snprintf()`. */ -int c_snprintf(char *buf, size_t buf_size, const char *format, ...); +int c_snprintf(char *buf, size_t buf_size, const char *format, ...) + PRINTF_LIKE(3, 4); /* * Equivalent of standard `vsnprintf()`. @@ -2185,7 +2315,8 @@ int mg_casecmp(const char *s1, const char *s2); * * The purpose of this is to avoid malloc-ing if generated strings are small. */ -int mg_asprintf(char **buf, size_t size, const char *fmt, ...); +int mg_asprintf(char **buf, size_t size, const char *fmt, ...) + PRINTF_LIKE(3, 4); /* Same as mg_asprintf, but takes varargs list. */ int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap); @@ -2208,6 +2339,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); @@ -3002,10 +3135,6 @@ struct { \ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_features.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_FEATURES_H_ #define CS_MONGOOSE_SRC_FEATURES_H_ @@ -3181,10 +3310,6 @@ struct { \ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net_if.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_NET_IF_H_ #define CS_MONGOOSE_SRC_NET_IF_H_ @@ -3237,10 +3362,12 @@ struct mg_iface_vtable { void (*connect_udp)(struct mg_connection *nc); /* Send functions for TCP and UDP. Sent data is copied before return. */ - void (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len); - void (*udp_send)(struct mg_connection *nc, const void *buf, size_t len); + int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len); + int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len); - void (*recved)(struct mg_connection *nc, size_t len); + int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len); + int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len, + union socket_address *sa, size_t *sa_len); /* Perform interface-related connection initialization. Return 1 on ok. */ int (*create_conn)(struct mg_connection *nc); @@ -3281,19 +3408,15 @@ void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa, /* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */ void mg_if_connect_cb(struct mg_connection *nc, int err); -/* Callback that reports that data has been put on the wire. */ -void mg_if_sent_cb(struct mg_connection *nc, int num_sent); /* - * Receive callback. - * if `own` is true, buf must be heap-allocated and ownership is transferred - * to the core. - * Core will acknowledge consumption by calling iface::recved. + * Callback that tells the core that data can be received. + * Core will use tcp/udp_recv to retrieve the data. */ -void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own); +void mg_if_can_recv_cb(struct mg_connection *nc); +void mg_if_can_send_cb(struct mg_connection *nc); /* * Receive callback. * buf must be heap-allocated and ownership is transferred to the core. - * Core will acknowledge consumption by calling iface::recved. */ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, union socket_address *sa, size_t sa_len); @@ -3301,10 +3424,13 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, /* void mg_if_close_conn(struct mg_connection *nc); */ /* Deliver a POLL event to the connection. */ -void mg_if_poll(struct mg_connection *nc, time_t now); +int mg_if_poll(struct mg_connection *nc, double now); -/* Deliver a TIMER event to the connection. */ -void mg_if_timer(struct mg_connection *c, double now); +/* + * Return minimal timer value amoung connections in the manager. + * Returns 0 if there aren't any timers. + */ +double mg_mgr_min_timer(const struct mg_mgr *mgr); #ifdef __cplusplus } @@ -3314,10 +3440,6 @@ void mg_if_timer(struct mg_connection *c, double now); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_ssl_if.h" #endif -/* - * Copyright (c) 2014-2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_SSL_IF_H_ #define CS_MONGOOSE_SRC_SSL_IF_H_ @@ -3331,7 +3453,7 @@ extern "C" { struct mg_ssl_if_ctx; struct mg_connection; -void mg_ssl_if_init(); +void mg_ssl_if_init(void); enum mg_ssl_if_result { MG_SSL_OK = 0, @@ -3372,22 +3494,6 @@ int mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_net.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* * === Core API: TCP/UDP/SSL @@ -3463,6 +3569,7 @@ struct mg_mgr { #endif void *user_data; /* User data */ int num_ifaces; + int num_calls; struct mg_iface **ifaces; /* network interfaces */ const char *nameserver; /* DNS server to use */ }; @@ -3483,9 +3590,6 @@ struct mg_connection { struct mbuf send_mbuf; /* Data scheduled for sending */ time_t last_io_time; /* Timestamp of the last socket IO */ double ev_timer_time; /* Timestamp of the future MG_EV_TIMER */ -#if MG_ENABLE_SSL - void *ssl_if_data; /* SSL library data. */ -#endif mg_event_handler_t proto_handler; /* Protocol-specific event handler */ void *proto_data; /* Protocol-specific data */ void (*proto_data_destructor)(void *proto_data); @@ -3513,20 +3617,30 @@ 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 */ #define MG_F_CLOSE_IMMEDIATELY (1 << 11) /* Disconnect */ -#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */ -#define MG_F_DELETE_CHUNK (1 << 13) /* HTTP specific */ + +/* Flags for protocol handlers */ +#define MG_F_PROTO_1 (1 << 12) +#define MG_F_PROTO_2 (1 << 13) #define MG_F_ENABLE_BROADCAST (1 << 14) /* Allow broadcast address usage */ -#define MG_F_USER_1 (1 << 20) /* Flags left for application */ +/* Flags left for application */ +#define MG_F_USER_1 (1 << 20) #define MG_F_USER_2 (1 << 21) #define MG_F_USER_3 (1 << 22) #define MG_F_USER_4 (1 << 23) #define MG_F_USER_5 (1 << 24) #define MG_F_USER_6 (1 << 25) + +#if MG_ENABLE_SSL + void *ssl_if_data; /* SSL library data. */ +#else + void *unused_ssl_if_data; /* To keep the size of the structure the same. */ +#endif }; /* @@ -3575,17 +3689,17 @@ void mg_mgr_init_opt(struct mg_mgr *mgr, void *user_data, * * Closes and deallocates all active connections. */ -void mg_mgr_free(struct mg_mgr *); +void mg_mgr_free(struct mg_mgr *mgr); /* * This function performs the actual IO and must be called in a loop - * (an event loop). It returns the current timestamp. + * (an event loop). It returns number of user events generated (except POLLs). * `milli` is the maximum number of milliseconds to sleep. * `mg_mgr_poll()` checks all connections for IO readiness. If at least one * of the connections is IO-ready, `mg_mgr_poll()` triggers the respective * event handlers and returns. */ -time_t mg_mgr_poll(struct mg_mgr *, int milli); +int mg_mgr_poll(struct mg_mgr *mgr, int milli); #if MG_ENABLE_BROADCAST /* @@ -3598,7 +3712,7 @@ time_t mg_mgr_poll(struct mg_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); @@ -3966,10 +4080,6 @@ double mg_time(void); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_uri.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === URI @@ -4036,10 +4146,6 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_util.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === Utility API @@ -4234,10 +4340,17 @@ void mg_basic_auth_header(const struct mg_str user, const struct mg_str pass, /* * URL-escape the specified string. - * All non-printable characters are escaped, plus `._-$,;~()/`. + * All characters acept letters, numbers and characters listed in + * `safe` are escaped. If `hex_upper`is true, `A-F` are used for hex digits. * Input need not be NUL-terminated, but the returned string is. * Returned string is heap-allocated and must be free()'d. */ +#define MG_URL_ENCODE_F_SPACE_AS_PLUS (1 << 0) +#define MG_URL_ENCODE_F_UPPERCASE_HEX (1 << 1) +struct mg_str mg_url_encode_opt(const struct mg_str src, + const struct mg_str safe, unsigned int flags); + +/* Same as `mg_url_encode_opt(src, "._-$,;~()/", 0)`. */ struct mg_str mg_url_encode(const struct mg_str src); #ifdef __cplusplus @@ -4247,10 +4360,6 @@ struct mg_str mg_url_encode(const struct mg_str src); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_http.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === Common API reference @@ -4261,8 +4370,8 @@ struct mg_str mg_url_encode(const struct mg_str src); #if MG_ENABLE_HTTP -/* Amalgamated: #include "mg_net.h" */ /* Amalgamated: #include "common/mg_str.h" */ +/* Amalgamated: #include "mg_net.h" */ #ifdef __cplusplus extern "C" { @@ -4329,6 +4438,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 */ @@ -4347,7 +4464,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 @@ -4361,6 +4478,9 @@ struct mg_ssi_call_ctx { #define MG_EV_HTTP_MULTIPART_REQUEST_END 125 #endif +#define MG_F_WEBSOCKET_NO_DEFRAG MG_F_PROTO_1 +#define MG_F_DELETE_CHUNK MG_F_PROTO_2 + /* * Attaches a built-in HTTP event handler to the given connection. * The user-defined event handler will receive following extra events: @@ -4386,7 +4506,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 *` * @@ -4680,9 +4802,9 @@ int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, size_t buf_size) #ifdef __GNUC__ - __attribute__((deprecated)); + __attribute__((deprecated)) #endif -; + ; /* * Gets and parses the Authorization: Basic header @@ -5243,22 +5365,6 @@ int mg_http_create_digest_auth_header(char *buf, size_t buf_len, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_mqtt.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* * === MQTT API reference @@ -5346,10 +5452,10 @@ struct mg_mqtt_proto_data { /* Message flags */ #define MG_MQTT_RETAIN 0x1 -#define MG_MQTT_DUP 0x4 #define MG_MQTT_QOS(qos) ((qos) << 1) #define MG_MQTT_GET_QOS(flags) (((flags) &0x6) >> 1) #define MG_MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1) +#define MG_MQTT_DUP 0x8 /* Connection flags */ #define MG_MQTT_CLEAN_SESSION 0x02 @@ -5474,22 +5580,6 @@ int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_mqtt_server.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* * === MQTT Server API reference @@ -5581,10 +5671,6 @@ struct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_dns.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === DNS API reference @@ -5707,6 +5793,7 @@ int mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr, * Encodes a DNS name. */ int mg_dns_encode_name(struct mbuf *io, const char *name, size_t len); +int mg_dns_encode_name_s(struct mbuf *io, struct mg_str name); /* Low-level: parses a DNS response. */ int mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg); @@ -5748,10 +5835,6 @@ void mg_set_protocol_dns(struct mg_connection *nc); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_dns_server.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === DNS server API reference @@ -5845,10 +5928,6 @@ void mg_dns_send_reply(struct mg_connection *nc, struct mg_dns_reply *r); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_resolv.h" #endif -/* - * Copyright (c) 2014 Cesanta Software Limited - * All rights reserved - */ /* * === API reference @@ -5929,22 +6008,6 @@ int mg_resolve_from_hosts_file(const char *host, union socket_address *usa); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_coap.h" #endif -/* - * Copyright (c) 2015 Cesanta Software Limited - * All rights reserved - * This software is dual-licensed: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. For the terms of this - * license, see . - * - * You are free to use this software under the terms of the GNU General - * Public License, but WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * Alternatively, you can license this software under a commercial - * license, as set out in . - */ /* * === CoAP API reference @@ -6097,10 +6160,6 @@ uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io); #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_sntp.h" #endif -/* - * Copyright (c) 2016 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_SNTP_H_ #define CS_MONGOOSE_SRC_SNTP_H_ @@ -6154,10 +6213,6 @@ struct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr, #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mg_socks.h" #endif -/* - * Copyright (c) 2017 Cesanta Software Limited - * All rights reserved - */ #ifndef CS_MONGOOSE_SRC_SOCKS_H_ #define CS_MONGOOSE_SRC_SOCKS_H_ diff --git a/net_services.c b/net_services.c index 89b8844..d9e6f31 100644 --- a/net_services.c +++ b/net_services.c @@ -191,7 +191,7 @@ void send_mqtt_msg(struct mg_connection *nc, char *toppic, char *message) //mg_mqtt_publish(nc, toppic, msg_id, MG_MQTT_QOS(0), message, strlen(message)); mg_mqtt_publish(nc, toppic, msg_id, MG_MQTT_RETAIN | MG_MQTT_QOS(1), message, strlen(message)); - logMessage(LOG_INFO, "MQTT: Published id=%d: %s %s\n", msg_id, toppic, message); + logMessage(LOG_DEBUG, "MQTT: Published id=%d: %s %s\n", msg_id, toppic, message); } void publish_zone_mqtt(struct mg_connection *nc, struct GPIOcfg *gpiopin) { @@ -322,6 +322,16 @@ void broadcast_sprinklerdactivestate(struct mg_connection *nc) } } } + + clearEventZones; +} + +int getTodayString(char *buffer, int size) { + time_t rawtime; + struct tm * timeinfo; + time (&rawtime); + timeinfo = localtime (&rawtime); + return strftime (buffer,size,"%a %h %d.",timeinfo); } void broadcast_sprinklerdstate(struct mg_connection *nc) @@ -332,6 +342,8 @@ void broadcast_sprinklerdstate(struct mg_connection *nc) static char mqtt_topic[250]; static char mqtt_msg[50]; static char last_state_msg[50]; + static int publishedTodayRainChance = -1; + static float publishedTodayRainTotal = -1; for (c = mg_next(nc->mgr, NULL); c != NULL; c = mg_next(nc->mgr, c)) { // Start from 0 since we publish master valve (just a temp measure) @@ -358,6 +370,30 @@ void broadcast_sprinklerdstate(struct mg_connection *nc) sprintf(mqtt_topic, "%s/status", _sdconfig_.mqtt_topic); sprinklerdstatus(mqtt_msg, 50); send_mqtt_msg(c, mqtt_topic, mqtt_msg); + if (publishedTodayRainChance != _sdconfig_.todayRainChance || isEventRainProbability) { + sprintf(mqtt_topic, "%s/chanceofrain", _sdconfig_.mqtt_topic); + sprintf(mqtt_msg, "%d",_sdconfig_.todayRainChance); + send_mqtt_msg(c, mqtt_topic, mqtt_msg); + publishedTodayRainChance = _sdconfig_.todayRainChance; + + getTodayString(mqtt_msg, 50); + sprintf(mqtt_topic, "%s/chanceofrain/day", _sdconfig_.mqtt_topic); + send_mqtt_msg(c, mqtt_topic, mqtt_msg); + + clearEventRainProbability; + } + if (publishedTodayRainTotal != _sdconfig_.todayRainTotal || isEventRainTotal) { + sprintf(mqtt_topic, "%s/raintotal", _sdconfig_.mqtt_topic); + sprintf(mqtt_msg, "%.2f",_sdconfig_.todayRainTotal); + send_mqtt_msg(c, mqtt_topic, mqtt_msg); + publishedTodayRainTotal = _sdconfig_.todayRainTotal; + + getTodayString(mqtt_msg, 50); + sprintf(mqtt_topic, "%s/raintotal/day", _sdconfig_.mqtt_topic); + send_mqtt_msg(c, mqtt_topic, mqtt_msg); + + clearEventRainTotal; + } } if (_sdconfig_.enableMQTTdz == true) { if (_sdconfig_.dzidx_calendar > 0 && update_dz_cache(_sdconfig_.dzidx_calendar,(_sdconfig_.calendar==true ? DZ_ON : DZ_OFF) )) { @@ -383,6 +419,9 @@ void broadcast_sprinklerdstate(struct mg_connection *nc) } } } + + clearEventStatus; + return; } @@ -402,7 +441,8 @@ bool is_value_flip(char *buf) { return false; } -int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, char *buffer, int size, bool *changedOption) { +//int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, char *buffer, int size, bool *changedOption) { +int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, char *buffer, int size) { static int buflen = 50; char buf[buflen]; //int action = -1; @@ -475,11 +515,12 @@ int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, c logMessage(LOG_WARNING, "Bad request unknown option\n"); length = sprintf(buffer, "{ \"error\": \"Bad request unknown option\" }"); } - *changedOption = true; + //*changedOption = true; + setEventStatus; //broadcast_sprinklerdstate(nc); } else if (strncasecmp(buf, "config", 6) == 0) { mg_get_http_var(&http_msg->query_string, "option", buf, buflen); - if (strncasecmp(buf, "raindelaychance", 15) == 0) + if (strncasecmp(buf, "raindelaychance", 15) == 0 ) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); _sdconfig_.precipChanceDelay = atoi(buf); @@ -499,9 +540,11 @@ int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, c mg_get_http_var(&http_msg->query_string, "sensor", buf, buflen); if (strncasecmp(buf, "chanceofrain", 13) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); + logMessage(LOG_NOTICE, "API: Chance of rain %d%%\n",atoi(buf)); setTodayChanceOfRain(atoi(buf)); } else if (strncasecmp(buf, "raintotal", 9) == 0) { mg_get_http_var(&http_msg->query_string, "value", buf, buflen); + logMessage(LOG_NOTICE, "API: Rain total %.2f\n",atof(buf)); setTodayRainTotal(atof(buf)); } length = build_sprinkler_JSON(buffer, size); @@ -519,13 +562,16 @@ int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, c //if ( (strncasecmp(buf, "off", 3) == 0 || strncmp(buf, "0", 1) == 0) && zone <= _sdconfig_.zones) { if (is_value_ON(buf) == true && zone <= _sdconfig_.zones) { + logMessage(LOG_NOTICE, "API: Turn zone %d on for %d mins\n",zone,runtime); zc_zone(type, zone, zcON, runtime); length = build_sprinkler_JSON(buffer, size); //} else if ( (strncasecmp(buf, "on", 2) == 0 || strncmp(buf, "1", 1) == 0) && zone <= _sdconfig_.zones) { } else if ( is_value_ON(buf) == false && zone <= _sdconfig_.zones) { + logMessage(LOG_NOTICE, "API: Turn zone %d off\n",zone); zc_zone(type, zone, zcOFF, runtime); length = build_sprinkler_JSON(buffer, size); } else if ( is_value_flip(buf) == true && zone <= _sdconfig_.zones) { + logMessage(LOG_NOTICE, "API: Turn zone %d %s\n",zone, !zc_state(zone)==zcON?"ON":"OFF"); zc_zone(type, zone, !zc_state(zone), runtime); } else { if (zone > _sdconfig_.zones) { @@ -535,6 +581,8 @@ int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, c logMessage(LOG_WARNING, "Bad request on zone %d, unknown state %s\n",zone, buf); length = sprintf(buffer, "{ \"error\": \"Bad request on zone %d, unknown state %s\"}", zone, buf); } + + setEventZones; } } else if (strcmp(buf, "zrtcfg") == 0) { //logMessage(LOG_DEBUG, "WEB REQUEST cfg %s\n",buf); @@ -547,7 +595,8 @@ int serve_web_request(struct mg_connection *nc, struct http_message *http_msg, c logMessage(LOG_DEBUG, "changed default runtime on zone %d, to %d\n",zone, runtime); length = build_sprinkler_JSON(buffer, size); zc_update_runtime(zone); - *changedOption = true; + //*changedOption = true; + setEventZones; } else length += sprintf(buffer, "{ \"error\": \"bad request zone %d runtime %d\"}",zone,runtime); } else if (strcmp(buf, "calcfg") == 0) { @@ -591,13 +640,14 @@ void action_web_request(struct mg_connection *nc, struct http_message *http_msg) char buf[bufsize]; char *c_type; int size = 0; - bool changedOption = false; + //bool changedOption = false; logMessage(LOG_DEBUG, "action_web_request %.*s %.*s\n",http_msg->uri.len, http_msg->uri.p, http_msg->query_string.len, http_msg->query_string.p); if (http_msg->query_string.len > 0) { //size = serve_web_request(nc, http_msg, buf, sizeof(buf)); - size = serve_web_request(nc, http_msg, buf, bufsize, &changedOption); + //size = serve_web_request(nc, http_msg, buf, bufsize, &changedOption); + size = serve_web_request(nc, http_msg, buf, bufsize); c_type = CT_JSON; if (size <= 0) { @@ -608,9 +658,8 @@ void action_web_request(struct mg_connection *nc, struct http_message *http_msg) mg_send(nc, buf, size); //logMessage (LOG_DEBUG, "Web Return %d = '%.*s'\n",size, size, buf); - if (changedOption) - _sdconfig_.eventToUpdateHappened = true; - //broadcast_sprinklerdstate(nc); + //if (changedOption) + // _sdconfig_.eventToUpdateHappened = true; return; } @@ -730,7 +779,8 @@ void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg){ if (zone > 0 && zone <= _sdconfig_.zones) { int v = str2int(msg->payload.p, msg->payload.len); _sdconfig_.zonecfg[zone].default_runtime = v / 60; - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + //setEventZones; zc_update_runtime(zone); logMessage(LOG_DEBUG, "MQTT: Default runtime zone %d is %d\n",zone,_sdconfig_.zonecfg[zone].default_runtime); } else { @@ -738,39 +788,42 @@ void action_mqtt_message(struct mg_connection *nc, struct mg_mqtt_message *msg){ } } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "24hdelay", 8) == 0 && strncmp(pt3, "set", 3) == 0 ) { enable_delay24h(status==zcON?true:false); - logMessage(LOG_DEBUG, "MQTT: Enable 24 hour delay %s\n",status==zcON?"YES":"NO"); + logMessage(LOG_NOTICE, "MQTT: Enable 24 hour delay %s\n",status==zcON?"YES":"NO"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "calendar", 6) == 0 && strncmp(pt3, "set", 3) == 0 ) { //_sdconfig_.system=status==zcON?true:false; - logMessage(LOG_NOTICE, "MQTT request to turn Calendar %s\n",status==zcON?"On":"Off"); + logMessage(LOG_DEBUG, "MQTT request to turn Calendar %s\n",status==zcON?"On":"Off"); enable_calendar(status==zcON?true:false); - logMessage(LOG_DEBUG, "MQTT: Turning calendar %s\n",status==zcON?"ON":"OFF"); + logMessage(LOG_NOTICE, "MQTT: Turning calendar %s\n",status==zcON?"ON":"OFF"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "cycleallzones", 13) == 0 && strncmp(pt3, "set", 3) == 0 ) { zc_zone(zcALL, 0, status, 0); - logMessage(LOG_DEBUG, "MQTT: Cycle all zones %s\n",status==zcON?"ON":"OFF"); + logMessage(LOG_NOTICE, "MQTT: Cycle all zones %s\n",status==zcON?"ON":"OFF"); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "raintotal", 9) == 0 && strncmp(pt3, "set", 3) == 0 ) { float v = str2float(msg->payload.p, msg->payload.len); - logMessage(LOG_DEBUG, "MQTT: Rain total %.2f\n",v); + logMessage(LOG_NOTICE, "MQTT: Rain total %.2f\n",v); setTodayRainTotal(v); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "chanceofrain", 12) == 0 && strncmp(pt3, "set", 3) == 0 ) { int v = str2int(msg->payload.p, msg->payload.len); - logMessage(LOG_DEBUG, "MQTT: Chance of rain %d%%\n",v); + logMessage(LOG_NOTICE, "MQTT: Chance of rain %d%%\n",v); setTodayChanceOfRain(v); } else if (pt2 != NULL && pt3 != NULL && strncmp(pt2, "zone", 4) == 0 && strncmp(pt3, "set", 3) == 0 ) { int zone = atoi(&pt2[4]); if (zone > 0 && zone <= _sdconfig_.zones) { + logMessage(LOG_NOTICE, "MQTT: Turn zone %d %s\n",zone, status==zcON?"ON":"OFF"); zc_zone(zcSINGLE, zone, status, 0); - logMessage(LOG_DEBUG, "MQTT: Turn zone %d %s\n",zone, status==zcON?"ON":"OFF"); } else if (zone == 0 && status == zcOFF && _sdconfig_.currentZone.type != zcNONE) { // request to turn off master will turn off any running zone - zc_zone(zcSINGLE, _sdconfig_.currentZone.zone , zcOFF, 0); logMessage(LOG_DEBUG, "MQTT: Request master valve off, turned zone %d off\n",_sdconfig_.currentZone.zone); + zc_zone(zcSINGLE, _sdconfig_.currentZone.zone , zcOFF, 0); } else if (zone == 0 && status == zcON) { // Can't turn on master valve, just re-send current stats. - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventZones; + setEventStatus; } else { logMessage(LOG_WARNING, "MQTT: unknown zone %d\n",zone); } } else { + // No set. nothing for us to do. logMessage(LOG_DEBUG, "MQTT: Unknown topic %.*s %.*s\n",msg->topic.len, msg->topic.p, msg->payload.len, msg->payload.p); return; } diff --git a/release/sprinklerd b/release/sprinklerd index 6f28a3e1903241c4aa898783eca34659b5afc0ce..37293a085019b67588a31ff3980a76f8a146b9aa 100755 GIT binary patch literal 211500 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 ey&HH=OZ0|dzlx7nM{b3Df6+@&5s>5`=>Q delta 100919 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 o(y+@=Q6M-dUzzKlFsSP#+= 0) { + //_sdconfig_.eventToUpdateHappened = true; + setEventRainProbability; _sdconfig_.todayRainChance = percent; if (_sdconfig_.precipChanceDelay > 0 && _sdconfig_.todayRainChance >= _sdconfig_.precipChanceDelay) { //enable_delay24h(true); - reset_delay24h_time(0); // will add 24hours or reset + reset_delay24h_time(0); // will add 24hours or turn on and set delay to 24hours + } else { + enable_delay24h(false); // Turn off rain delay } return true; } @@ -28,10 +32,12 @@ bool setTodayChanceOfRain(int percent) bool setTodayRainTotal(float rain) { - if (_sdconfig_.todayRainTotal == rain) + if (_sdconfig_.todayRainTotal == rain && rain != 0) return true; - - _sdconfig_.todayRainTotal = rain; + + _sdconfig_.todayRainTotal = rain; + //_sdconfig_.eventToUpdateHappened = true; + setEventRainTotal; logMessage(LOG_DEBUG, "Today's rain total = %f\n",_sdconfig_.todayRainTotal); @@ -64,7 +70,8 @@ bool check_delay24h() void enable_calendar(bool state) { if (_sdconfig_.calendar != state) { - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventStatus; _sdconfig_.calendar = state; logMessage(LOG_NOTICE, "Turning %s calendar\n",state==true?"on":"off"); } else { @@ -75,7 +82,8 @@ void enable_calendar(bool state) void enable_delay24h(bool state) { if (_sdconfig_.delay24h != state) { - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventStatus; _sdconfig_.delay24h = state; if (state) { @@ -96,7 +104,8 @@ void enable_delay24h(bool state) void reset_delay24h_time(unsigned long dtime) { if (_sdconfig_.delay24h != true) { - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventStatus; } time_t now; @@ -136,6 +145,7 @@ void write_cron() { int hour; int day; int zone; + int rb4i; bool fs = remount_root_ro(false); @@ -170,7 +180,26 @@ void write_cron() { } } } + + for (rb4i=0; rb4i<_sdconfig_.runBeforeCmds; rb4i++) { + if (_sdconfig_.cron[day].minute < _sdconfig_.runBeforeCmd[rb4i].mins) { + fprintf(fp, "%d %d * * %d root %s\n", + (60 - (_sdconfig_.runBeforeCmd[rb4i].mins - _sdconfig_.cron[day].minute)), + (_sdconfig_.cron[day].hour - 1), + day, + _sdconfig_.runBeforeCmd[rb4i].command); + } else { + fprintf(fp, "%d %d * * %d root %s\n", + (_sdconfig_.cron[day].minute - _sdconfig_.runBeforeCmd[rb4i].mins), + _sdconfig_.cron[day].hour, + day, + _sdconfig_.runBeforeCmd[rb4i].command); + } + } } + + + fprintf(fp, "0 0 * * * root /usr/bin/curl -s -o /dev/null 'localhost:%s?type=sensor&sensor=chanceofrain&value=0'\n",_sdconfig_.socket_port); fprintf(fp, "0 0 * * * root /usr/bin/curl -s -o /dev/null 'localhost:%s?type=sensor&sensor=raintotal&value=0'\n",_sdconfig_.socket_port); fprintf(fp, "#***** AUTO GENERATED DO NOT EDIT *****\n"); @@ -205,7 +234,7 @@ void read_cron() { for (day=0; day <= 6; day++) { _sdconfig_.cron[day].hour = -1; _sdconfig_.cron[day].minute = -1; - for (zone=1; zone < _sdconfig_.zones; zone ++) { + for (zone=0; zone < _sdconfig_.zones; zone ++) { _sdconfig_.cron[day].zruntimes[zone] = 0; } } diff --git a/sprinkler.c b/sprinkler.c index bdd92ec..bed345b 100644 --- a/sprinkler.c +++ b/sprinkler.c @@ -148,7 +148,8 @@ int main (int argc, char *argv[]) _sdconfig_.calendar = true; _sdconfig_.currentZone.type = zcNONE; _sdconfig_.cron_update = 0; - _sdconfig_.eventToUpdateHappened = false; + //_sdconfig_.eventToUpdateHappened = false; + _sdconfig_.updateEventMask = 0; read_cron(); read_cache(); @@ -303,10 +304,10 @@ void main_loop () { //logMessage (LOG_DEBUG, "mg_mgr_poll\n"); mg_mgr_poll(&_mgr, 500); - + //logMessage (LOG_DEBUG, "updateEventMask=%d\n",_sdconfig_.updateEventMask); check_cron(); - if (zc_check() == true || check_delay24h() == true || _sdconfig_.eventToUpdateHappened) { - _sdconfig_.eventToUpdateHappened = false; + if (zc_check() == true || check_delay24h() == true || _sdconfig_.updateEventMask != 0) { + //_sdconfig_.eventToUpdateHappened = false; broadcast_sprinklerdstate(_mgr.active_connections); broadcast_sprinklerdactivestate(_mgr.active_connections); } else if (i > 10 && _sdconfig_.currentZone.type!=zcNONE) { diff --git a/version.h b/version.h index 00ecef1..c54b72a 100644 --- a/version.h +++ b/version.h @@ -1,6 +1,6 @@ #ifndef SD_VERSION_H #define SD_VERSION_H -#define SD_VERSION "1.3" +#define SD_VERSION "1.4.1" #endif diff --git a/zone_ctrl.c b/zone_ctrl.c index 3d63567..cf96979 100644 --- a/zone_ctrl.c +++ b/zone_ctrl.c @@ -42,6 +42,8 @@ int start_next_zone(int startz) { int zone = startz+1; + setEventZones; + while( _sdconfig_.zonecfg[zone].default_runtime <= 0 || !validGPIO( _sdconfig_.zonecfg[zone].pin) ) { //logMessage (LOG_INFO, "Run Zone, skipping zone %d due to runtime of %d\n",zone,_sdconfig_.zonecfg[zone].default_runtime); logMessage (LOG_INFO, "Run Zone, skipping zone %d due to %s\n",zone,_sdconfig_.zonecfg[zone].default_runtime<=0?" runtime of 0":" bad GPIO pin#"); @@ -74,6 +76,7 @@ void zc_update_runtime(int zone) { if (zone > 0 && zone < _sdconfig_.zones && zone == _sdconfig_.currentZone.zone) { _sdconfig_.currentZone.duration=_sdconfig_.zonecfg[zone].default_runtime; } + setEventZones; } bool zc_check() { @@ -236,7 +239,8 @@ bool zc_start(/*zcRunType type,*/ int zone) { digitalWrite(_sdconfig_.zonecfg[zone].pin, _sdconfig_.zonecfg[zone].on_state ); int rtn = true; #endif - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventZones; return rtn; // store what's running @@ -264,7 +268,8 @@ bool zc_stop(/*zcRunType type,*/ int zone) { digitalWrite(_sdconfig_.zonecfg[zone].pin, !_sdconfig_.zonecfg[zone].on_state ); int rtn = true; #endif - _sdconfig_.eventToUpdateHappened = true; + //_sdconfig_.eventToUpdateHappened = true; + setEventZones; return rtn; /*