diff --git a/payload/user/remote_access/pager-webui/server.py b/payload/user/remote_access/pager-webui/server.py index d4d1f2b..bd1b099 100644 --- a/payload/user/remote_access/pager-webui/server.py +++ b/payload/user/remote_access/pager-webui/server.py @@ -1646,29 +1646,41 @@ def _uci_section(section): def h_pineap_wifi_get_ap(ctx): - open_cfg = _uci_wifi_iface('wlan0open') - radio_cfg = _uci_wifi_iface('radio0') - wpa_cfg = _uci_wifi_iface('wlan0wpa') + def _iface_state(open_name, wpa_name, radio_name): + open_cfg = _uci_wifi_iface(open_name) + wpa_cfg = _uci_wifi_iface(wpa_name) + radio_cfg = _uci_wifi_iface(radio_name) + + def _chan(cfg): + channel = cfg.get('channel') or '' + try: + return int(channel) + except (TypeError, ValueError): + return None + + open_channel = _chan(open_cfg) + wpa_channel = _chan(wpa_cfg) + encryption = wpa_cfg.get('encryption') or '' + if encryption.startswith('psk2'): + encryption = 'psk2' + elif encryption.startswith('sae'): + encryption = 'sae' + elif encryption.startswith('owe'): + encryption = 'owe' + return open_cfg, wpa_cfg, radio_cfg, open_channel, wpa_channel, encryption + + if _uci_wifi_iface('wlan1open') or _uci_wifi_iface('wlan1wpa'): + open_cfg, wpa_cfg, radio_cfg, open_channel, wpa_channel, encryption = _iface_state( + 'wlan1open', 'wlan1wpa', 'radio1') + else: + open_cfg, wpa_cfg, radio_cfg, open_channel, wpa_channel, encryption = _iface_state( + 'wlan0open', 'wlan0wpa', 'radio0') status, data = daemon_sock_call('GET', '/api/pineap/hostapd/get_config') host = data if status == 200 and isinstance(data, dict) else {} status2, data2 = daemon_sock_call('GET', '/api/pineap/get_config') pinecfg = data2 if status2 == 200 and isinstance(data2, dict) else {} pool = _uci_section('pineapd.@ssidpool[0]') - channel = radio_cfg.get('channel') or '' - try: - channel = int(channel) - except (TypeError, ValueError): - channel = None - encryption = wpa_cfg.get('encryption') or '' - # OpenWrt commonly decorates the key-management value with a cipher - # (for example, "psk2+ccmp"). The UI exposes the logical modes, so - # normalize the live UCI value to an option the select can represent. - if encryption.startswith('psk2'): - encryption = 'psk2' - elif encryption.startswith('sae'): - encryption = 'sae' - elif encryption.startswith('owe'): - encryption = 'owe' + radio1 = _uci_wifi_iface('radio1') return 200, { 'open': { 'enabled': open_cfg.get('disabled') == '0', @@ -1676,7 +1688,7 @@ def h_pineap_wifi_get_ap(ctx): 'bssid': open_cfg.get('macaddr') or '', 'target': pool.get('target') or None, 'hidden': open_cfg.get('hidden') == '1', - 'channel': channel, + 'channel': open_channel, 'country': radio_cfg.get('country') or '', }, 'wpa': { @@ -1685,9 +1697,17 @@ def h_pineap_wifi_get_ap(ctx): 'enctype': encryption, 'hidden': wpa_cfg.get('hidden') == '1', 'enabled': wpa_cfg.get('disabled') == '0', + 'channel': wpa_channel, }, 'enterprise': {'enabled': not host.get('pineape_disabled', True)}, 'pool': {'disabled': None, 'collecting': bool(pinecfg.get('autossidpool'))}, + 'radio1': { + 'band': {'2g': BAND_2G, '5g': BAND_5G, '6g': BAND_6G}.get( + (radio1 or {}).get('band'), BAND_5G), + 'channel': (radio1 or {}).get('channel') or 'auto', + 'htmode': (radio1 or {}).get('htmode') or 'VHT80', + 'country': (radio1 or {}).get('country') or '', + }, } diff --git a/tests/test_pineap_bands.py b/tests/test_pineap_bands.py index e23a19c..caa3612 100644 --- a/tests/test_pineap_bands.py +++ b/tests/test_pineap_bands.py @@ -71,5 +71,93 @@ class BandAuxTest(unittest.TestCase): set(range(52, 65, 4)) | set(range(100, 145, 4))) +def ctx(body=None): + return type('C', (), {'body': body, 'args': (), 'query': {}})() + + +class GetApRadio1Test(unittest.TestCase): + def _uci(self, section): + table = { + 'radio0': {'type': 'wifi-device', 'band': '2g', 'channel': '11', + 'htmode': 'HT20', 'country': 'US'}, + 'radio1': {'type': 'wifi-device', 'band': '5g', 'channel': 'auto', + 'htmode': 'VHT80', 'country': 'US'}, + 'wlan0open': {'device': 'radio0', 'mode': 'ap', 'ssid': 'pager-open', + 'disabled': '0', 'hidden': '0', 'encryption': 'none', + 'channel': '11'}, + 'wlan0wpa': {'device': 'radio0', 'mode': 'ap', 'ssid': 'Service', + 'disabled': '0', 'hidden': '0', 'encryption': 'psk2', + 'channel': '1', 'key': 'testpass123'}, + 'wlan1open': {'device': 'radio1', 'mode': 'ap', 'ssid': 'CorpGuest', + 'disabled': '0', 'hidden': '0', 'encryption': 'none', + 'channel': '36'}, + 'wlan1wpa': {'device': 'radio1', 'mode': 'ap', 'ssid': 'Corp', + 'disabled': '0', 'hidden': '0', 'encryption': 'sae', + 'channel': '1', 'key': 'secret123'}, + } + return dict(table.get(section, {})) + + def setUp(self): + server._uci_wifi_iface = lambda name: self._uci(name) + server.daemon_sock_call = lambda method, path, body=None, timeout=10: ( + 404, {'error': 'not found'}) + + def test_open_reports_radio1_when_present(self): + status, payload = server.h_pineap_wifi_get_ap(ctx()) + self.assertEqual(status, 200) + self.assertEqual(payload['open']['ssid'], 'CorpGuest') + self.assertEqual(payload['open']['channel'], 36) + self.assertEqual(payload['open']['country'], 'US') + + def test_wpa_reports_radio1_when_present(self): + status, payload = server.h_pineap_wifi_get_ap(ctx()) + self.assertEqual(status, 200) + self.assertEqual(payload['wpa']['ssid'], 'Corp') + self.assertEqual(payload['wpa']['enctype'], 'sae') + self.assertEqual(payload['wpa']['channel'], 1) + + def test_radio1_info(self): + status, payload = server.h_pineap_wifi_get_ap(ctx()) + self.assertEqual(status, 200) + self.assertEqual(payload['radio1']['band'], server.BAND_5G) + self.assertEqual(payload['radio1']['channel'], 'auto') + + +class GetApRadio1AbsentTest(unittest.TestCase): + """Regression: no radio1 AP sections -> today's 2.4GHz behavior.""" + + def _uci(self, section): + table = { + 'radio0': {'type': 'wifi-device', 'band': '2g', 'channel': '11', + 'htmode': 'HT20', 'country': 'US'}, + 'radio1': {'type': 'wifi-device', 'band': '5g', 'channel': 'auto', + 'htmode': 'VHT80', 'country': 'US'}, + 'wlan0open': {'device': 'radio0', 'mode': 'ap', 'ssid': 'pager-open', + 'disabled': '0', 'hidden': '0', 'encryption': 'none', + 'channel': '11'}, + 'wlan0wpa': {'device': 'radio0', 'mode': 'ap', 'ssid': 'Service', + 'disabled': '0', 'hidden': '0', 'encryption': 'psk2', + 'channel': '1', 'key': 'testpass123'}, + } + return dict(table.get(section, {})) + + def setUp(self): + server._uci_wifi_iface = lambda name: self._uci(name) + server.daemon_sock_call = lambda method, path, body=None, timeout=10: ( + 404, {'error': 'not found'}) + + def test_open_uses_wlan0open(self): + status, payload = server.h_pineap_wifi_get_ap(ctx()) + self.assertEqual(status, 200) + self.assertEqual(payload['open']['ssid'], 'pager-open') + self.assertEqual(payload['open']['channel'], 11) + + def test_wpa_uses_wlan0wpa(self): + status, payload = server.h_pineap_wifi_get_ap(ctx()) + self.assertEqual(status, 200) + self.assertEqual(payload['wpa']['ssid'], 'Service') + self.assertEqual(payload['wpa']['channel'], 1) + + if __name__ == '__main__': unittest.main()