@@ -278,9 +278,163 @@ NETEOF"
278278 done
279279 [ " $CONVERGED " = " false" ] && { err " WiFi Convergence Failed (L2=$CONN_STATE , GW=${GW:- none} )" ; exit 1; }
280280
281- info " --- [PHASE 6b] Client Mode Restoration ---"
281+ # =======================================================================
282+ # PHASE 7: WiFi Disconnect / Reconnect Cycle
283+ # Use case: User switches networks, reconnects after going offline
284+ # =======================================================================
285+ info " --- [PHASE 7] WiFi Disconnect & Reconnect ---"
286+ info " Phase 7.1: Explicit disconnect..."
287+ m_exec " $CLIENT " rxnm wifi disconnect --interface " $CLI_WLAN " || true
288+ sleep 2
289+ DISC_STATE=$( m_exec " $CLIENT " iwctl station " $CLI_WLAN " show 2> /dev/null | awk ' /State/{print $NF}' | tr ' [:upper:]' ' [:lower:]' || echo " unknown" )
290+ if [ " $DISC_STATE " = " disconnected" ] || [ " $DISC_STATE " = " autoconnect_quick" ] || [ " $DISC_STATE " = " autoconnect_full" ]; then
291+ info " ✓ Disconnect confirmed (state: $DISC_STATE )"
292+ else
293+ warn " Disconnect state unexpected: $DISC_STATE (non-fatal)"
294+ fi
295+
296+ info " Phase 7.2: Reconnect to saved network..."
297+ m_exec " $CLIENT " rxnm wifi connect " RXNM_Test_Net" --password " supersecret" --interface " $CLI_WLAN " || true
298+ sleep 5
299+ CONVERGED=false
300+ for i in $( seq 1 15) ; do
301+ CONN_STATE=$( m_exec " $CLIENT " iwctl station " $CLI_WLAN " show 2> /dev/null | awk ' /State/{print $NF}' | tr ' [:upper:]' ' [:lower:]' || echo " unknown" )
302+ if [ " $CONN_STATE " = " connected" ]; then
303+ GW=$( m_exec " $CLIENT " ip -4 route show default dev " $CLI_WLAN " 2> /dev/null | awk ' /via/{print $3}' | head -n1)
304+ if [ -n " $GW " ]; then
305+ if m_exec " $CLIENT " ping -c 1 -W 2 " $GW " > /dev/null 2>&1 ; then
306+ info " ✓ Reconnect successful (gateway: $GW )"
307+ CONVERGED=true; break
308+ fi
309+ fi
310+ fi
311+ sleep 2
312+ done
313+ [ " $CONVERGED " = " false" ] && { err " Reconnect failed (L2=$CONN_STATE , GW=${GW:- none} )" ; exit 1; }
314+
315+ # =======================================================================
316+ # PHASE 8: WiFi Nullify (Project Silence over WiFi)
317+ # Use case: Handheld entering deep sleep, silencing WiFi traffic
318+ # =======================================================================
319+ info " --- [PHASE 8] Project Silence over WiFi ---"
320+ info " Phase 8.1: Enable nullify on client WiFi..."
321+ m_exec " $CLIENT " rxnm system nullify enable --interface " $CLI_WLAN " --xdp yes --wowlan no --bt no || warn " Nullify enable failed (expected if XDP unsupported in nspawn)"
322+ sleep 2
323+
324+ info " Phase 8.2: Verify traffic blocked..."
325+ if m_exec " $CLIENT " ping -c 1 -W 1 192.168.212.1 > /dev/null 2>&1 ; then
326+ warn " Nullify bypass (XDP not supported on hwsim in nspawn — non-fatal)"
327+ else
328+ info " ✓ Traffic dropped by XDP"
329+ fi
330+
331+ info " Phase 8.3: Disable nullify and verify restoration..."
332+ m_exec " $CLIENT " rxnm system nullify disable --interface " $CLI_WLAN " || true
333+ sleep 3
334+
335+ # Reconnect if nullify dropped the connection
336+ CONN_STATE=$( m_exec " $CLIENT " iwctl station " $CLI_WLAN " show 2> /dev/null | awk ' /State/{print $NF}' | tr ' [:upper:]' ' [:lower:]' || echo " unknown" )
337+ if [ " $CONN_STATE " != " connected" ]; then
338+ info " Phase 8.3a: Reconnecting after nullify..."
339+ m_exec " $CLIENT " rxnm wifi connect " RXNM_Test_Net" --password " supersecret" --interface " $CLI_WLAN " || true
340+ sleep 5
341+ fi
342+
343+ RESTORED=false
344+ for i in $( seq 1 10) ; do
345+ if m_exec " $CLIENT " ping -c 1 -W 2 192.168.212.1 > /dev/null 2>&1 ; then
346+ info " ✓ Network restored after nullify"
347+ RESTORED=true; break
348+ fi
349+ sleep 2
350+ done
351+ [ " $RESTORED " = " false" ] && { err " Network not restored after nullify disable" ; exit 1; }
352+
353+ # =======================================================================
354+ # PHASE 9: WiFi Credential Management
355+ # Use case: User forgets a saved network
356+ # =======================================================================
357+ info " --- [PHASE 9] Credential Management ---"
358+ info " Phase 9.1: List known networks..."
359+ KNOWN=$( m_exec " $CLIENT " rxnm wifi networks --format json 2> /dev/null || echo " {}" )
360+ if echo " $KNOWN " | grep -q " RXNM_Test_Net" ; then
361+ info " ✓ RXNM_Test_Net found in known networks"
362+ else
363+ warn " RXNM_Test_Net not in known networks list (IWD may not persist in ephemeral container)"
364+ fi
365+
366+ info " Phase 9.2: Forget network..."
367+ m_exec " $CLIENT " rxnm wifi forget " RXNM_Test_Net" --yes 2> /dev/null || true
368+ sleep 1
369+ KNOWN_AFTER=$( m_exec " $CLIENT " rxnm wifi networks --format json 2> /dev/null || echo " {}" )
370+ if echo " $KNOWN_AFTER " | grep -q " RXNM_Test_Net" ; then
371+ warn " Network still in known list after forget (non-fatal in ephemeral container)"
372+ else
373+ info " ✓ Network forgotten successfully"
374+ fi
375+
376+ # =======================================================================
377+ # PHASE 10: Open AP (No Password)
378+ # Use case: Quick local multiplayer session without WPA overhead
379+ # =======================================================================
380+ info " --- [PHASE 10] Open AP Mode ---"
381+ info " Phase 10.1: Disconnect client and stop previous AP..."
382+ m_exec " $CLIENT " rxnm wifi disconnect --interface " $CLI_WLAN " 2> /dev/null || true
383+ m_exec " $SERVER " rxnm wifi client --interface " $SRV_WLAN " 2> /dev/null || true
384+ sleep 3
385+
386+ info " Phase 10.2: Start open AP..."
387+ m_exec " $SERVER " rxnm wifi ap start " RXNM_Open_Net" --share --interface " $SRV_WLAN " || true
388+ sleep 2
389+
390+ # Re-apply server AP config (same nspawn workaround as Phase 6)
391+ m_exec " $SERVER " /bin/bash -c " cat > /run/systemd/network/50-wifi-ap-test.network << 'NETEOF'
392+ [Match]
393+ Name=$SRV_WLAN
394+
395+ [Network]
396+ Description=CI Open AP Test
397+ Address=192.168.212.1/24
398+ DHCPServer=yes
399+ LinkLocalAddressing=yes
400+ ConfigureWithoutCarrier=yes
401+
402+ [DHCPServer]
403+ PoolOffset=10
404+ PoolSize=50
405+ EmitDNS=yes
406+ EmitRouter=yes
407+ NETEOF"
408+ m_exec " $SERVER " networkctl reload 2> /dev/null || true
409+ sleep 1
410+ m_exec " $SERVER " networkctl reconfigure " $SRV_WLAN " 2> /dev/null || true
411+ sleep 3
412+
413+ info " Phase 10.3: Scan and connect to open AP..."
414+ sleep 3
415+ SCAN_OPEN=$( m_exec " $CLIENT " rxnm wifi scan --interface " $CLI_WLAN " --format json || echo " {}" )
416+ if echo " $SCAN_OPEN " | grep -q " RXNM_Open_Net" ; then
417+ info " ✓ Open AP detected in scan"
418+ else
419+ warn " Open AP not detected in scan (non-fatal)"
420+ fi
421+
422+ # Open network connect (no password)
423+ m_exec " $CLIENT " rxnm wifi connect " RXNM_Open_Net" --interface " $CLI_WLAN " || true
424+ sleep 5
425+ OPEN_STATE=$( m_exec " $CLIENT " iwctl station " $CLI_WLAN " show 2> /dev/null | awk ' /State/{print $NF}' | tr ' [:upper:]' ' [:lower:]' || echo " unknown" )
426+ if [ " $OPEN_STATE " = " connected" ]; then
427+ info " ✓ Connected to open AP"
428+ else
429+ warn " Open AP connect state: $OPEN_STATE (non-fatal — open AP may not work on all IWD versions)"
430+ fi
431+
432+ # =======================================================================
433+ # PHASE 11: Client Mode Restoration (Final Cleanup)
434+ # Use case: Returning to station mode after hotspot/AP session
435+ # =======================================================================
436+ info " --- [PHASE 11] Final Client Mode Restoration ---"
282437 m_exec " $SERVER " rxnm wifi client --interface " $SRV_WLAN " || true
283- # AP → station mode transition on hwsim is slow; poll with retries
284438 SRV_MODE=" ap"
285439 for i in $( seq 1 10) ; do
286440 sleep 2
@@ -290,10 +444,11 @@ NETEOF"
290444 fi
291445 done
292446 if [ " $SRV_MODE " = " station" ] || [ " $SRV_MODE " = " unknown" ]; then
293- info " ✓ AP state cleaned up successfully (mode: $SRV_MODE )"
447+ info " ✓ Server restored to station mode (mode: $SRV_MODE )"
294448 else
295449 warn " AP mode transition slow (mode: $SRV_MODE ) — non-fatal in CI"
296450 fi
451+
297452elif [ " $SKIP_WIFI " = " false" ]; then
298453 info " --- [PHASE 6] SKIPPED (Virtual WiFi module unavailable) ---"
299454fi
0 commit comments