diff --git a/src/net/nl_api.c b/src/net/nl_api.c index aa502fe..2555e9c 100644 --- a/src/net/nl_api.c +++ b/src/net/nl_api.c @@ -10,6 +10,9 @@ #define P_SETTINGS "/api/v1/system/settings" #define P_COMMAND "/api/v1/system/command" +/* Obergrenze fuer das AP-Passwort des Standalone-Modus (WPA2-ASCII-Passphrase). */ +#define NL_AP_PASS_MAX 63 + static const char *const POB_NAMES[] = {"last", "on", "off"}; static void reply(nl_api_response_t *r, int status, const char *json) @@ -370,7 +373,12 @@ static void handle_put_settings(const nl_api_ctx_t *ctx, const nl_http_request_t will_enable = cJSON_IsBool(en) && cJSON_IsTrue(en); asp = get_string(j, "ssid", 1, NL_SSID_LEN - 1, &as); - app = get_string(j, "password", 8, NL_PASS_LEN - 1, &ap); + /* 63, nicht NL_PASS_LEN - 1 (64): eine WPA2-ASCII-Passphrase darf hoechstens 63 + * Zeichen haben. Bei 64 schriebe der Treiber keyinfo.key[64] randvoll ohne + * Abschlussbyte, und ob libwlan.a das annimmt, sieht man dem Binaerobjekt nicht an - + * ein abgelehnter AP-Aufbau ist hier aber eine ueber die API erreichbare Sackgasse. + * Der "wifi"-Zweig bleibt bewusst bei 64: dort haengt kein eigener AP daran. */ + app = get_string(j, "password", 8, NL_AP_PASS_MAX, &ap); chp = get_int(j, "channel", 1, 13, &ch); if (!cJSON_IsBool(en)) { diff --git a/src/net/nl_wifi_w600.c b/src/net/nl_wifi_w600.c index 0107203..3ba7b53 100644 --- a/src/net/nl_wifi_w600.c +++ b/src/net/nl_wifi_w600.c @@ -41,6 +41,12 @@ static uint8_t s_ap_channel = 6; static char s_ap_live_ssid[NL_SSID_LEN], s_ap_live_pass[NL_PASS_LEN]; static uint8_t s_ap_live_channel; static bool s_ap_live; +/* Drei Versuche, dann der Rueckfall auf Daten, die schon einmal getragen haben: einer koennte + * ein Zufall sein (das Funkteil raeumt gerade den alten AP ab), aber drei hintereinander + * heissen, dass das Funkteil genau diese Daten nicht annimmt. Und 3 x 5 s hoechstens + * 15 s ohne AP ist kuerzer als jede Fehlersuche vor Ort. */ +#define NL_AP_CREATE_TRIES 3 +static uint8_t s_ap_create_fails; /* Vom HTTP-Task gefuellt, vom Netz-Task uebernommen; beides unter s_lock. */ static char s_req_ap_ssid[NL_SSID_LEN], s_req_ap_pass[NL_PASS_LEN]; static uint8_t s_req_ap_channel; @@ -104,6 +110,16 @@ void nl_wifi_disable_sdk_auto_connect(void) tls_wifi_disconnect(); } +/* Setzt Name, Passwort und Kanal des naechsten AP auf die Vorgaben zurueck: "ninlight-" + * mit dem einkompilierten Passwort auf Kanal 6. Beruehrt einen LAUFENDEN AP nicht - der + * behaelt seine Daten, bis er abgebaut und neu aufgebaut wird. */ +static void reset_ap_defaults(void) +{ + s_ap_ssid[0] = '\0'; + copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); + s_ap_channel = 6; +} + /* Reisst den AP ab und raeumt dabei auch die Buchfuehrung auf. Laeuft nur im Netz-Task * (oder vor dessen Start) und nie unter s_lock - clear_softap_param() fasst SDK-Zustand an. */ static void destroy_ap(void) @@ -159,7 +175,28 @@ static void start_ap(void) copy(s_ap_live_ssid, ssid, sizeof s_ap_live_ssid); copy(s_ap_live_pass, s_ap_pass, sizeof s_ap_live_pass); s_ap_live_channel = s_ap_channel; + s_ap_create_fails = 0; + return; } + /* Nimmt das Funkteil diese Daten dauerhaft nicht an, steht die Lampe ohne AP da - und mit + * in den Einstellungen gespeicherten Wunschdaten auch nach einem Neustart wieder. Dann + * lieber ein AP unter unerwartetem Namen: der ist sichtbar und reparierbar. */ + if (++s_ap_create_fails < NL_AP_CREATE_TRIES) + return; + s_ap_create_fails = 0; + if (s_ap_live_ssid[0] && (strcmp(s_ap_live_ssid, ssid) != 0 || + strcmp(s_ap_live_pass, s_ap_pass) != 0 || + s_ap_live_channel != s_ap_channel)) { + printf("wifi: AP create keeps failing, falling back to the last working data (%s, ch %u)\r\n", + s_ap_live_ssid, (unsigned)s_ap_live_channel); + copy(s_ap_ssid, s_ap_live_ssid, sizeof s_ap_ssid); + copy(s_ap_pass, s_ap_live_pass, sizeof s_ap_pass); + s_ap_channel = s_ap_live_channel; + } else if (s_ap_ssid[0] || s_ap_channel != 6 || strcmp(s_ap_pass, NINLIGHT_AP_PASSWORD) != 0) { + printf("wifi: AP create keeps failing, falling back to %s on ch 6\r\n", s_hostname); + reset_ap_defaults(); + } + /* Sonst scheitern schon die Vorgaben - dann bleibt nur, es weiter zu versuchen. */ } static void connect_station(bool test) @@ -201,6 +238,12 @@ static void execute(nl_wifi_actions_t a) /* Sonst stuende die Lampe nach dem naechsten Neustart wieder in ihrem eigenen AP, * obwohl sie gerade erfolgreich ins WLAN gewechselt ist. */ printf("wifi: standalone mode cleared\r\n"); + /* Dieselbe Rueckstellung wie beim Zurueckschalten ueber die API: sonst kaeme ein + * spaeterer Rueckfall-AP (5 min ohne Station) mit Namen und Passwort des Nutzers + * hoch, obwohl standalone in den Einstellungen schon 0 ist. Gefahrlos, weil dieses + * Aktionsbuendel kein start_ap enthaelt - der noch laufende Standalone-AP bleibt + * unberuehrt, bis die Zustandsmaschine ihn abbaut. */ + reset_ap_defaults(); nl_app_clear_standalone(); } if (a.pulse_on) @@ -270,10 +313,10 @@ static void net_task(void *arg) } else { /* Zurueck auf die Vorgaben: ein spaeterer Rueckfall- oder Onboarding-AP ist * wieder der gewohnte "ninlight-" mit dem einkompilierten Passwort, - * genau wie bei einer Lampe, die nie standalone war. */ - s_ap_ssid[0] = '\0'; - copy(s_ap_pass, NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); - s_ap_channel = 6; + * genau wie bei einer Lampe, die nie standalone war. Das greift erst beim + * NAECHSTEN Aufbau - der noch laufende AP behaelt Name und Passwort, bis ihn + * die Zustandsmaschine abbaut. */ + reset_ap_defaults(); ev = nl_wifi_sm_leave_standalone(&s_sm, s_ssid[0] != '\0', now); } s_req_pending = false; @@ -327,8 +370,10 @@ void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass, nl_names_hostname(s_mac, s_hostname, sizeof s_hostname); copy(s_ssid, ssid, sizeof s_ssid); copy(s_pass, pass, sizeof s_pass); - copy(s_ap_pass, ap_pass, sizeof s_ap_pass); - copy(s_ap_ssid, ap_ssid, sizeof s_ap_ssid); /* leer = Hostname */ + /* Wie in nl_wifi_request_standalone(): NULL ist hier zwar von keinem Aufrufer zu + * erwarten, aber copy() wuerde darueber stolpern. */ + copy(s_ap_pass, ap_pass && ap_pass[0] ? ap_pass : NINLIGHT_AP_PASSWORD, sizeof s_ap_pass); + copy(s_ap_ssid, ap_ssid ? ap_ssid : "", sizeof s_ap_ssid); /* leer = Hostname */ s_ap_channel = ap_channel ? ap_channel : 6; /* The SDK would otherwise reconnect on its own with credentials from its parameter area. */ diff --git a/test/test_api.c b/test/test_api.c index 78fcd6f..8c9a832 100644 --- a/test/test_api.c +++ b/test/test_api.c @@ -728,11 +728,18 @@ static void test_standalone_length_limits(void) { char body[256], pw[80], ss[48]; - memset(pw, 'p', 64); pw[64] = '\0'; + /* 63 ist die Obergrenze, nicht 64: eine WPA2-ASCII-Passphrase ist hoechstens 63 Zeichen + * lang, und ein vom Funkteil abgelehnter AP-Aufbau waere hier eine Sackgasse. */ + memset(pw, 'p', 63); pw[63] = '\0'; snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body); TEST_ASSERT_EQUAL_INT(202, resp.status); TEST_ASSERT_EQUAL_STRING(pw, fake_standalone_pass); + memset(pw, 'p', 64); pw[64] = '\0'; + snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw); + call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body); + TEST_ASSERT_EQUAL_INT(400, resp.status); + TEST_ASSERT_NULL(strstr(resp.body, pw)); memset(pw, 'p', 65); pw[65] = '\0'; snprintf(body, sizeof body, "{\"type\":\"standalone\",\"enabled\":true,\"password\":\"%s\"}", pw); call(NL_HTTP_PUT, "/api/v1/system/settings", TOKEN, body);