fix: Recovery-Rettungsweg gegen Endlosschleife, Werksreset und Fremdnetz absichern
C1: nl_recovery_try() verglich nichts mit der laufenden Firmware. Vor einer laufenden Rettungsstation - laut Spec 9 der Normalfall, solange eine Charge migriert - lud die Lampe bei jedem Boot dasselbe Image erneut und startete neu. nl_ota_start_recovery() setzt jetzt ein Flag, das nl_fwup_verify_tail() nur im Recovery-Pfad auswerten lässt: Trägt der Trailer dieselbe Version wie die laufende Firmware (nl_trailer_version(), bisher ungenutzt), wird das Image abgelehnt und der Boot läuft normal weiter. Ein absichtliches Neuflashen derselben Version über die REST-API bleibt möglich. Kein neuer Wert im last_ota_error-Vokabular (Spec 8.2); unterschieden wird über die Konsole. C1/I2: boot_ms wurde erst nach nl_recovery_try() gesetzt und der Power-Cycle-Zähler erst 5 s danach gelöscht - bei erfolgreicher Recovery also nie. Nach NL_FACTORY_RESET_BOOTS Runden folgte ein Werksreset. boot_ms startet jetzt am Anfang von app_task (damit gilt wieder das in Spec 3.4/5.5 zugesagte 5-s-Fenster statt faktisch ~8 s), und nl_app_clear_reset_counter() löscht den Zähler zusätzlich, sobald die Rettungsstation gefunden ist. I3: tls_sys_auto_mode_run() stößt direkt nach UserMain() einen SDK-eigenen Verbindungsversuch an; das dabei entstehende NETIF_IP_NET_UP war in recovery_join_event() nicht vom eigenen Join zu unterscheiden, die Lampe hätte im falschen Netz geladen. nl_wifi_disable_sdk_auto_connect() schaltet das Flag ab und kappt eine bestehende Verbindung jetzt am Anfang von nl_recovery_try(); zusätzlich prüft nl_wifi_join_blocking() über tls_wifi_get_current_bss(), dass die Verbindung wirklich zur erwarteten SSID gehört.
This commit is contained in:
+15
-1
@@ -109,9 +109,21 @@ void nl_recovery_try(void)
|
||||
{
|
||||
char url[128];
|
||||
|
||||
/* I3: the SDK's auto-connect may already be joining its own stored AP in parallel (see
|
||||
nl_wifi_disable_sdk_auto_connect()). Switch it off and drop that connection before we
|
||||
scan, so everything that follows is our own doing. */
|
||||
nl_wifi_disable_sdk_auto_connect();
|
||||
|
||||
if (!recovery_ap_present())
|
||||
return;
|
||||
printf("recovery: station found, joining\r\n");
|
||||
/* C1: from here on this boot is a recovery boot - it may end in a flash + reset that never
|
||||
returns to app_task, so the power-cycle counter must be cleared now instead of after the
|
||||
5 s deadline. Otherwise every recovery round would count as another short power cycle and
|
||||
NL_FACTORY_RESET_BOOTS rounds in front of the station would wipe Wi-Fi, token and name.
|
||||
Trade-off: while a rescue station is in range, the "5 x power cycle" factory reset does
|
||||
not work - a lamp in front of the station is being serviced, not being reset. */
|
||||
nl_app_clear_reset_counter();
|
||||
if (!nl_wifi_join_blocking(NINLIGHT_RECOVERY_SSID, NINLIGHT_RECOVERY_PASSWORD, NL_RECOVERY_JOIN_MS)) {
|
||||
printf("recovery: join failed\r\n");
|
||||
nl_ota_set_error("download");
|
||||
@@ -119,7 +131,9 @@ void nl_recovery_try(void)
|
||||
}
|
||||
snprintf(url, sizeof url, "http://192.168.8.1/ninlight_%s_ota.img", NINLIGHT_DEVICE_ID);
|
||||
printf("recovery: fetching %s\r\n", url);
|
||||
if (nl_ota_start(url) != 0) {
|
||||
/* C1: recovery refuses an image that carries the running version - the station serves the
|
||||
same file to every lamp and stays up for a whole migration batch. */
|
||||
if (nl_ota_start_recovery(url) != 0) {
|
||||
printf("recovery: cannot start download\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ extern volatile uint32_t nl_net_beat;
|
||||
|
||||
/* Starts the network task. An empty ssid starts the onboarding AP. Strings are copied. */
|
||||
void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass);
|
||||
/* Switches the SDK's own auto-connect off and drops any connection it may already have brought
|
||||
* up on its own. nl_wifi_start() does this too, but the boot recovery path runs before that and
|
||||
* must not have the SDK joining its stored AP in parallel (I3, final review). Safe to call any
|
||||
* number of times; only writes the SDK parameter when it is actually set. */
|
||||
void nl_wifi_disable_sdk_auto_connect(void);
|
||||
/* Joins a station directly (no nl_wifi_sm) and blocks until it gets an IP or timeout_ms
|
||||
* elapses; returns true on success. For use before nl_wifi_start() only - e.g. the boot
|
||||
* recovery path in nl_recovery.c - since it drives the SDK Wi-Fi calls itself instead of
|
||||
|
||||
+36
-7
@@ -69,6 +69,25 @@ static void clear_softap_param(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* I3: tls_sys_auto_mode_run() (sdk/Platform/Sys/tls_sys.c) kicks off an SDK-driven join right
|
||||
* after UserMain() whenever the persisted auto-connect flag is on - and tls_sys.c:264-267 turns a
|
||||
* temporary WIFI_AUTO_CNT_TMP_OFF back into WIFI_AUTO_CNT_ON and persists that, so the factory
|
||||
* default "off" is not something we may rely on. Any such join is indistinguishable from our own
|
||||
* in the netif status events, so switch it off (and drop a connection it already made) before the
|
||||
* recovery scan, not only in nl_wifi_start(). */
|
||||
void nl_wifi_disable_sdk_auto_connect(void)
|
||||
{
|
||||
u8 auto_mode = WIFI_AUTO_CNT_OFF;
|
||||
|
||||
tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_GET, &auto_mode);
|
||||
if (auto_mode != WIFI_AUTO_CNT_OFF) {
|
||||
printf("wifi: SDK auto-connect was %u, switching it off\r\n", auto_mode);
|
||||
auto_mode = WIFI_AUTO_CNT_OFF;
|
||||
tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_SET, &auto_mode);
|
||||
}
|
||||
tls_wifi_disconnect();
|
||||
}
|
||||
|
||||
static void start_ap(void)
|
||||
{
|
||||
struct tls_softap_info_t ap;
|
||||
@@ -220,7 +239,6 @@ static void net_task(void *arg)
|
||||
|
||||
void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass)
|
||||
{
|
||||
u8 auto_mode = WIFI_AUTO_CNT_OFF;
|
||||
nl_wifi_actions_t a;
|
||||
struct netif *nif;
|
||||
|
||||
@@ -233,12 +251,7 @@ void nl_wifi_start(const char *ssid, const char *pass, const char *ap_pass)
|
||||
copy(s_ap_pass, ap_pass, sizeof s_ap_pass);
|
||||
|
||||
/* The SDK would otherwise reconnect on its own with credentials from its parameter area. */
|
||||
tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_GET, &auto_mode);
|
||||
if (auto_mode != WIFI_AUTO_CNT_OFF) {
|
||||
auto_mode = WIFI_AUTO_CNT_OFF;
|
||||
tls_wifi_auto_connect_flag(WIFI_AUTO_CNT_FLAG_SET, &auto_mode);
|
||||
}
|
||||
tls_wifi_disconnect();
|
||||
nl_wifi_disable_sdk_auto_connect();
|
||||
if (tls_wifi_softap_get_state() != WM_WIFI_DISCONNECTED) {
|
||||
/* C2: the SDK already brought up its own softap (e.g. from NETIF_IP_NET_UP before
|
||||
* we got a chance to run) - tear it down, our state machine owns AP lifecycle. */
|
||||
@@ -369,5 +382,21 @@ bool nl_wifi_join_blocking(const char *ssid, const char *pass, uint32_t timeout_
|
||||
tls_wifi_disconnect(); /* cancel a stuck/timed-out join attempt */
|
||||
return false;
|
||||
}
|
||||
/* I3 backstop: NETIF_IP_NET_UP carries no SSID, so an SDK-initiated join to its own stored AP
|
||||
* would look exactly like ours - and the lamp would then fetch http://192.168.8.1/... from a
|
||||
* foreign network. tls_wifi_get_current_bss() does report the SSID actually associated with
|
||||
* (sdk/Include/WiFi/wm_wifi.h:190), so verify it before the caller starts a download. */
|
||||
{
|
||||
struct tls_curr_bss_t bss;
|
||||
size_t n = strlen(ssid);
|
||||
|
||||
memset(&bss, 0, sizeof bss);
|
||||
tls_wifi_get_current_bss(&bss);
|
||||
if (bss.ssid_len != (u8)n || memcmp(bss.ssid, ssid, n) != 0) {
|
||||
printf("wifi: joined a different network than %s, dropping the connection\r\n", ssid);
|
||||
tls_wifi_disconnect();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user