blob: e13e1b828e850169b21d3bf66d01e0b7f483ccea [file]
/*
*
* Copyright (c) 2026 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WpaSupplicantClient.h"
#include <algorithm>
#include <mutex>
#include <utility>
#include <vector>
#include <lib/support/CHIPMemString.h>
#include <platform/CHIPDeviceConfig.h>
#include <platform/CHIPDeviceEvent.h>
#include <platform/CHIPDeviceLayer.h>
#include <platform/DiagnosticDataProvider.h>
#include <platform/NetworkCommissioning.h>
#include <platform/PlatformManager.h>
#include <system/SystemLayer.h>
#include "ConnectivityManagerImpl.h"
#include "NetworkCommissioningDriver.h"
#include "WirelessDefs.h"
using namespace ::chip::DeviceLayer::NetworkCommissioning;
using namespace ::chip::app::Clusters::WiFiNetworkDiagnostics;
namespace chip {
namespace DeviceLayer {
namespace Internal {
namespace {
static constexpr char kWpaSupplicantServiceName[] = "fi.w1.wpa_supplicant1";
static constexpr char kWpaSupplicantObjectPath[] = "/fi/w1/wpa_supplicant1";
// wpa_supplicant's scan results don't contains the channel infomation, so we need this lookup table for resolving the band and
// channel infomation.
static std::pair<WiFiBand, uint16_t> GetBandAndChannelFromFrequency(const uint32_t & freq) noexcept
{
std::pair<WiFiBand, uint16_t> ret = std::make_pair(WiFiBand::k2g4, 0);
if (freq <= 931)
{
ret.first = WiFiBand::k1g;
if (freq >= 916)
{
ret.second = ((freq - 916) * 2) - 1;
}
else if (freq >= 902)
{
ret.second = (freq - 902) * 2;
}
else if (freq >= 863)
{
ret.second = (freq - 863) * 2;
}
else
{
ret.second = 1;
}
}
else if (freq <= 2472)
{
ret.second = static_cast<uint16_t>((freq - 2412) / 5 + 1);
}
else if (freq == 2484)
{
ret.second = 14;
}
else if (freq >= 3600 && freq <= 3700)
{
// Note: There are not many devices supports this band, and this band contains rational frequency in MHz, need to figure out
// the behavior of wpa_supplicant in this case.
ret.first = WiFiBand::k3g65;
}
else if (freq >= 5035 && freq <= 5945)
{
ret.first = WiFiBand::k5g;
ret.second = static_cast<uint16_t>((freq - 5000) / 5);
}
else if (freq == 5960 || freq == 5980)
{
ret.first = WiFiBand::k5g;
ret.second = static_cast<uint16_t>((freq - 5000) / 5);
}
else if (freq >= 5955)
{
ret.first = WiFiBand::k6g;
ret.second = static_cast<uint16_t>((freq - 5950) / 5);
}
else if (freq >= 58000)
{
ret.first = WiFiBand::k60g;
// Note: Some channel has the same center frequency but different bandwidth. Should figure out wpa_supplicant's behavior in
// this case. Also, wpa_supplicant's frequency property is uint16 infact.
switch (freq)
{
case 58'320:
ret.second = 1;
break;
case 60'480:
ret.second = 2;
break;
case 62'640:
ret.second = 3;
break;
case 64'800:
ret.second = 4;
break;
case 66'960:
ret.second = 5;
break;
case 69'120:
ret.second = 6;
break;
case 59'400:
ret.second = 9;
break;
case 61'560:
ret.second = 10;
break;
case 63'720:
ret.second = 11;
break;
case 65'880:
ret.second = 12;
break;
case 68'040:
ret.second = 13;
break;
}
}
return ret;
}
} // namespace
void WpaSupplicantClient::GDBusWpaSupplicant::Reset()
{
iface.reset();
proxy.reset();
interfacePath.reset();
networkPath.reset();
}
CHIP_ERROR WpaSupplicantClient::Init(ConnectivityManagerImpl & inConnectivityManagerImpl) noexcept
{
// Initially, and again at some future point, this should have an
// assertion that mConnectivityManagerImpl is null and otherwise
// return CHIP_ERROR_ALREADY_INITIALIZED to indicate to the caller
// that 'Init' has been double-tripped without a bookending call
// to Shutdown. However, the ConnectivityManager stack of which
// this is a part has no peer 'Shutdown' method to go with its
// 'Init' and subsequently, there is not yet a reasonable place to
// hook the 'Shutdown' for this class.
//
// At some point, revisit the top-down Init/Shutdown in
// ConnectivityManager. When that happens, plumb the call
// propagation through to this client 'Shutdown' and re-add the
// assertion.
mAssociationStarted = false;
mAssociationRetriesLeft = 0;
mWiFiIfName[0] = '\0';
mInterestedSSID.reset();
mConnectivityManagerImpl = &inConnectivityManagerImpl;
return CHIP_NO_ERROR;
}
void WpaSupplicantClient::Shutdown() noexcept
{
Reset();
mConnectivityManagerImpl = nullptr;
}
void WpaSupplicantClient::Reset() noexcept
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
mWpaSupplicant.Reset();
}
bool WpaSupplicantClient::IsStarted() const noexcept
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
return !!mWpaSupplicant.iface;
}
bool WpaSupplicantClient::IsWiFiInterfaceEnabled() const noexcept
{
VerifyOrReturnValue(mWpaSupplicant.iface, false);
// Check if the interface is not disabled (for example, due to rfkill or some other reasons).
return g_strcmp0(wpa_supplicant_1_interface_get_state(mWpaSupplicant.iface.get()), "interface_disabled") != 0;
}
bool WpaSupplicantClient::GetBssInfo(const gchar * inBssPath,
NetworkCommissioning::WiFiScanResponse & outWiFiScanResponse) const noexcept
{
// This function can be called without g_main_context_get_thread_default() being set.
// The BSS proxy object is created in a synchronous manner, so the D-Bus call will be
// completed before this function returns. Also, no external callbacks are registered
// with the proxy object.
GAutoPtr<GError> err;
GAutoPtr<WpaSupplicant1BSS> bss(wpa_supplicant_1_bss_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, inBssPath, nullptr, &err.GetReceiver()));
if (bss == nullptr)
{
return false;
}
WpaSupplicant1BSSProxy * bssProxy = WPA_SUPPLICANT_1_BSS_PROXY(bss.get());
GAutoPtr<GVariant> ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID"));
GAutoPtr<GVariant> bssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "BSSID"));
// Network scan is performed in the background, so the BSS
// may be gone when we try to get the properties.
if (ssid == nullptr || bssid == nullptr)
{
ChipLogDetail(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "BSS not found: %s", StringOrNullMarker(inBssPath));
return false;
}
gsize ssidLen = 0;
gsize bssidLen = 0;
char bssidStr[2 * 6 + 5 + 1] = { 0 };
auto ssidStr = reinterpret_cast<const uint8_t *>(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(uint8_t)));
auto bssidBuf = reinterpret_cast<const uint8_t *>(g_variant_get_fixed_array(bssid.get(), &bssidLen, sizeof(uint8_t)));
gint16 signal = wpa_supplicant_1_bss_get_signal(bss.get());
guint16 frequency = wpa_supplicant_1_bss_get_frequency(bss.get());
if (bssidLen == 6)
{
snprintf(bssidStr, sizeof(bssidStr), "%02x:%02x:%02x:%02x:%02x:%02x", bssidBuf[0], bssidBuf[1], bssidBuf[2], bssidBuf[3],
bssidBuf[4], bssidBuf[5]);
}
else
{
ChipLogError(DeviceLayer,
WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Got a network with incorrect BSSID len: %" G_GSIZE_FORMAT " != 6", bssidLen);
bssidLen = 0;
}
ChipLogDetail(DeviceLayer, "Network Found: %s (%s) Signal:%d",
NullTerminated(StringOrNullMarker((const gchar *) ssidStr), ssidLen).c_str(), bssidStr, signal);
// Internal sentinel (bit 7). Not a real WiFiSecurityBitmap value; keeps an EAP-only
// network from being reported as Open. Masked off before the result is returned.
// TODO: The following code will mistakenly recognize WEP encryption as OPEN network, this should be fixed by reading
// IEs (information elements) field instead of reading cooked data.
static constexpr chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> kEAP{ static_cast<uint8_t>(1 << 7) };
auto IsNetworkWPAPSK = [](GVariant * wpa) -> chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> {
chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> res;
if (wpa == nullptr)
{
return res;
}
GAutoPtr<GVariant> keyMgmt(g_variant_lookup_value(wpa, "KeyMgmt", nullptr));
if (keyMgmt == nullptr)
{
return res;
}
GBorrowedStrvPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr));
const gchar ** keyMgmtsHandle = keyMgmts.get();
VerifyOrReturnError(keyMgmtsHandle != nullptr, res);
for (auto keyMgmtVal = *keyMgmtsHandle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHandle))
{
if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-none") == 0)
{
res.Set(app::Clusters::NetworkCommissioning::WiFiSecurityBitmap::kWpaPersonal);
}
else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0)
{
res.Set(kEAP);
}
}
return res;
};
auto IsNetworkWPA2PSK = [](GVariant * rsn) -> chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> {
chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> res;
if (rsn == nullptr)
{
return res;
}
GAutoPtr<GVariant> keyMgmt(g_variant_lookup_value(rsn, "KeyMgmt", nullptr));
if (keyMgmt == nullptr)
{
return res;
}
GBorrowedStrvPtr keyMgmts(g_variant_get_strv(keyMgmt.get(), nullptr));
const gchar ** keyMgmtsHandle = keyMgmts.get();
VerifyOrReturnError(keyMgmtsHandle != nullptr, res);
for (auto keyMgmtVal = *keyMgmtsHandle; keyMgmtVal != nullptr; keyMgmtVal = *(++keyMgmtsHandle))
{
if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-psk-sha256") == 0 ||
g_strcasecmp(keyMgmtVal, "wpa-ft-psk") == 0)
{
res.Set(app::Clusters::NetworkCommissioning::WiFiSecurityBitmap::kWpa2Personal);
}
else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0 || g_strcasecmp(keyMgmtVal, "wpa-eap-sha256") == 0 ||
g_strcasecmp(keyMgmtVal, "wpa-ft-eap") == 0)
{
res.Set(kEAP);
}
else if (g_strcasecmp(keyMgmtVal, "sae") == 0)
{
// wpa_supplicant will include "sae" in KeyMgmt field for WPA3 WiFi, this is not included in the wpa_supplicant
// document.
res.Set(app::Clusters::NetworkCommissioning::WiFiSecurityBitmap::kWpa3Personal);
}
}
return res;
};
auto GetNetworkSecurityType =
[IsNetworkWPAPSK, IsNetworkWPA2PSK](
WpaSupplicant1BSSProxy * proxy) -> chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> {
GAutoPtr<GVariant> wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA"));
GAutoPtr<GVariant> rsn(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "RSN"));
chip::BitFlags<app::Clusters::NetworkCommissioning::WiFiSecurityBitmap> res(IsNetworkWPAPSK(wpa.get()),
IsNetworkWPA2PSK(rsn.get()));
if (!res.HasAny())
{
res.Set(app::Clusters::NetworkCommissioning::WiFiSecurityBitmap::kUnencrypted);
}
res.Clear(kEAP);
return res;
};
// Drop the network if its SSID or BSSID is illegal.
VerifyOrReturnError(ssidLen <= DeviceLayer::Internal::kMaxWiFiSSIDLength, false);
VerifyOrReturnError(bssidLen == kWiFiBSSIDLength, false);
memcpy(outWiFiScanResponse.ssid, ssidStr, ssidLen);
memcpy(outWiFiScanResponse.bssid, bssidBuf, bssidLen);
outWiFiScanResponse.ssidLen = ssidLen;
outWiFiScanResponse.signal.type = WirelessSignalType::kdBm;
outWiFiScanResponse.signal.strength = static_cast<int8_t>(std::clamp<gint16>(signal, INT8_MIN, INT8_MAX));
auto bandInfo = GetBandAndChannelFromFrequency(frequency);
outWiFiScanResponse.wiFiBand = bandInfo.first;
outWiFiScanResponse.channel = bandInfo.second;
outWiFiScanResponse.security = GetNetworkSecurityType(bssProxy);
return true;
}
CHIP_ERROR WpaSupplicantClient::GetConfiguredNetwork(NetworkCommissioning::Network & outNetwork) noexcept
{
// This function can be called without g_main_context_get_thread_default() being set.
// The network proxy object is created in a synchronous manner, so the D-Bus call will
// be completed before this function returns. Also, no external callbacks are registered
// with the proxy object.
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
GAutoPtr<GError> err;
if (!mWpaSupplicant.iface)
{
ChipLogDetail(DeviceLayer, "Wifi network not currently connected");
return CHIP_ERROR_INCORRECT_STATE;
}
const char * networkPath = wpa_supplicant_1_interface_get_current_network(mWpaSupplicant.iface.get());
// wpa_supplicant DBus API: if network path of current network is "/", means no networks is currently selected.
if ((networkPath == nullptr) || (strcmp(networkPath, "/") == 0))
{
return CHIP_ERROR_KEY_NOT_FOUND;
}
GAutoPtr<WpaSupplicant1Network> networkInfo(wpa_supplicant_1_network_proxy_new_for_bus_sync(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, networkPath, nullptr, &err.GetReceiver()));
VerifyOrReturnError(
networkInfo, CHIP_ERROR_INTERNAL,
ChipLogError(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Failed to create network proxy: %s", err->message));
outNetwork.connected = wpa_supplicant_1_network_get_enabled(networkInfo.get());
GVariant * properties = wpa_supplicant_1_network_get_properties(networkInfo.get());
VerifyOrReturnError(properties != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
GAutoPtr<GVariant> ssid(g_variant_lookup_value(properties, "ssid", nullptr));
VerifyOrReturnError(ssid, CHIP_ERROR_KEY_NOT_FOUND);
gsize length;
const gchar * ssidStr = g_variant_get_string(ssid.get(), &length);
// TODO: wpa_supplicant will return ssid with quotes! We should have a better way to get the actual ssid in bytes.
gsize length_actual = length - 2;
VerifyOrReturnError(length_actual <= sizeof(outNetwork.networkID), CHIP_ERROR_INTERNAL);
ChipLogDetail(DeviceLayer, "Current connected network: %s", StringOrNullMarker(ssidStr));
memcpy(outNetwork.networkID, ssidStr + 1, length_actual);
outNetwork.networkIDLen = length_actual;
return CHIP_NO_ERROR;
}
CHIP_ERROR WpaSupplicantClient::GetIfName(CharSpan & outIfName) const noexcept
{
outIfName = CharSpan::fromCharString(mWiFiIfName);
return CHIP_NO_ERROR;
}
CHIP_ERROR WpaSupplicantClient::SetIfName(const CharSpan & inIfName) noexcept
{
VerifyOrReturnError(inIfName.size() < std::size(mWiFiIfName), CHIP_ERROR_BUFFER_TOO_SMALL);
Platform::CopyString(mWiFiIfName, inIfName);
return CHIP_NO_ERROR;
}
CHIP_ERROR WpaSupplicantClient::ScanNetwork(const ByteSpan & inSsid)
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
VerifyOrReturnError(mWpaSupplicant.iface, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(inSsid.size() <= mInterestedSSID.capacity(), CHIP_ERROR_INVALID_ARGUMENT);
GAutoPtr<GError> err;
GVariant * args = nullptr;
GVariantBuilder builder;
mInterestedSSID = inSsid;
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "Type", g_variant_new_string("active"));
args = g_variant_builder_end(&builder);
if (!wpa_supplicant_1_interface_call_scan_sync(mWpaSupplicant.iface.get(), args, nullptr, &err.GetReceiver()))
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "failed to start network scan: %s",
err ? err->message : "unknown error");
mInterestedSSID.reset();
return CHIP_ERROR_INTERNAL;
}
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "initialized network scan");
return CHIP_NO_ERROR;
}
void WpaSupplicantClient::OnWpaPropertiesChanged(WpaSupplicant1Interface * iface, GVariant * changedProperties)
{
const char * state = nullptr;
// We are only interested in the "State" property changes.
VerifyOrReturn(g_variant_lookup(changedProperties, "State", "&s", &state));
WiFiDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetWiFiDiagnosticsDelegate();
ChipLogDetail(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Interface properties changed, state is '%s'", state);
if (g_strcmp0(state, "associating") == 0)
{
mAssociationStarted = true;
}
else if (g_strcmp0(state, "disconnected") == 0)
{
int reason = wpa_supplicant_1_interface_get_disconnect_reason(iface);
ChipLogDetail(DeviceLayer,
WPA_SUPPLICANT_CLIENT_LOG_PREFIX
"Disconnected with reason code=%d, assoc status code=%d, auth status code=%d (associationStarted=%d)",
reason, wpa_supplicant_1_interface_get_assoc_status_code(iface),
wpa_supplicant_1_interface_get_auth_status_code(iface), mAssociationStarted);
if (delegate != nullptr)
{
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([delegate, reason]() {
delegate->OnDisconnectionDetected(reason);
delegate->OnConnectionStatusChanged(static_cast<uint8_t>(ConnectionStatusEnum::kNotConnected));
});
}
if (mAssociationStarted && mConnectivityManagerImpl != nullptr)
{
uint8_t associationFailureCause = static_cast<uint8_t>(AssociationFailureCauseEnum::kUnknown);
uint16_t status = WLAN_STATUS_UNSPECIFIED_FAILURE;
if (wpa_supplicant_1_interface_get_assoc_status_code(iface) == WLAN_STATUS_AUTH_TIMEOUT)
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
/* Handle intermittent association failures */
if (mAssociationRetriesLeft > 0)
{
mAssociationRetriesLeft--;
ChipLogDetail(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Association timeout, %u retries left",
mAssociationRetriesLeft);
mAssociationStarted = false;
GAutoPtr<GError> err;
if (!wpa_supplicant_1_interface_call_select_network_sync(
mWpaSupplicant.iface.get(), mWpaSupplicant.networkPath.get(), nullptr, &err.GetReceiver()))
{
// Fallthrough to existing error handling code as we could not retry.
ChipLogError(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Failed to select network: '%s'", err->message);
}
else
{
// Skip existing error handling code to not report error too early to the network commissioning cluster.
return;
}
}
}
switch (abs(reason))
{
case WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY:
case WLAN_REASON_DISASSOC_AP_BUSY:
case WLAN_REASON_DISASSOC_STA_HAS_LEFT:
case WLAN_REASON_DISASSOC_LOW_ACK:
case WLAN_REASON_BSS_TRANSITION_DISASSOC:
associationFailureCause = static_cast<uint8_t>(AssociationFailureCauseEnum::kAssociationFailed);
status = wpa_supplicant_1_interface_get_assoc_status_code(iface);
break;
case WLAN_REASON_PREV_AUTH_NOT_VALID:
case WLAN_REASON_DEAUTH_LEAVING:
case WLAN_REASON_IEEE_802_1X_AUTH_FAILED:
associationFailureCause = static_cast<uint8_t>(AssociationFailureCauseEnum::kAuthenticationFailed);
status = wpa_supplicant_1_interface_get_auth_status_code(iface);
break;
default:
break;
}
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([this, reason]() {
mConnectivityManagerImpl->OnConnectResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), reason);
});
if (delegate != nullptr)
{
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([delegate, associationFailureCause, status]() {
delegate->OnAssociationFailureDetected(associationFailureCause, status);
});
}
}
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([this]() { this->UpdateWiFiNetworkStatus(); });
NotifyWiFiConnectivityChange(kConnectivity_Lost);
mAssociationStarted = false;
}
else if (g_strcmp0(state, "associated") == 0)
{
if (delegate != nullptr)
{
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda(
[delegate]() { delegate->OnConnectionStatusChanged(static_cast<uint8_t>(ConnectionStatusEnum::kConnected)); });
}
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([this]() { this->UpdateWiFiNetworkStatus(); });
}
else if (g_strcmp0(state, "completed") == 0)
{
if (mAssociationStarted && mConnectivityManagerImpl != nullptr)
{
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([this]() {
mConnectivityManagerImpl->OnConnectResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0);
PostNetworkConnect();
});
}
NotifyWiFiConnectivityChange(kConnectivity_Established);
mAssociationStarted = false;
}
}
void WpaSupplicantClient::OnWpaInterfaceProxyReady(GObject * sourceObject, GAsyncResult * res)
{
// When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
GAutoPtr<GError> err;
mWpaSupplicant.iface.reset(wpa_supplicant_1_interface_proxy_new_for_bus_finish(res, &err.GetReceiver()));
if (mWpaSupplicant.iface && err == nullptr)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "connected to wpa_supplicant interface proxy");
g_signal_connect(
mWpaSupplicant.iface.get(), "g-properties-changed",
G_CALLBACK(+[](WpaSupplicant1Interface * iface, GVariant * properties, const char * const * invalidatedProps,
WpaSupplicantClient * self) { return self->OnWpaPropertiesChanged(iface, properties); }),
this);
g_signal_connect(mWpaSupplicant.iface.get(), "scan-done",
G_CALLBACK(+[](WpaSupplicant1Interface * iface, gboolean success, WpaSupplicantClient * self) {
return self->OnWpaInterfaceScanDone(iface, success);
}),
this);
}
else
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "failed to create wpa_supplicant interface proxy %s: %s",
mWpaSupplicant.interfacePath.get(), err ? err->message : "unknown error");
}
// We need to stop auto scan or it will block our network scan.
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda([this]() {
CHIP_ERROR errInner = StopAutoScan();
if (errInner != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Failed to stop auto scan: %" CHIP_ERROR_FORMAT,
errInner.Format());
}
});
}
void WpaSupplicantClient::OnWpaProxyReady(GObject * sourceObject, GAsyncResult * res)
{
// When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
GAutoPtr<GError> err;
mWpaSupplicant.proxy.reset(wpa_supplicant_1_proxy_new_for_bus_finish(res, &err.GetReceiver()));
if (mWpaSupplicant.proxy && err.get() == nullptr)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "connected to wpa_supplicant proxy");
g_signal_connect(mWpaSupplicant.proxy.get(), "interface-added",
G_CALLBACK(+[](WpaSupplicant1 * proxy, const char * path, GVariant * properties,
WpaSupplicantClient * self) { return self->OnWpaInterfaceAdded(proxy, path, properties); }),
this);
g_signal_connect(mWpaSupplicant.proxy.get(), "interface-removed",
G_CALLBACK(+[](WpaSupplicant1 * proxy, const char * path, WpaSupplicantClient * self) {
return self->OnWpaInterfaceRemoved(proxy, path);
}),
this);
wpa_supplicant_1_call_get_interface(
mWpaSupplicant.proxy.get(), mWiFiIfName, nullptr,
reinterpret_cast<GAsyncReadyCallback>(+[](GObject * sourceObject_, GAsyncResult * res_, WpaSupplicantClient * self) {
return self->OnWpaInterfaceReady(sourceObject_, res_);
}),
this);
}
else
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "failed to create wpa_supplicant proxy %s",
err ? err->message : "unknown error");
}
}
void WpaSupplicantClient::OnWpaInterfaceRemoved(WpaSupplicant1 * proxy, const char * path)
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
if (g_strcmp0(mWpaSupplicant.interfacePath.get(), path) == 0)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "WiFi interface removed: %s", StringOrNullMarker(path));
mWpaSupplicant.interfacePath.reset();
mWpaSupplicant.iface.reset();
}
}
void WpaSupplicantClient::OnWpaInterfaceAdded(WpaSupplicant1 * proxy, const char * path, GVariant * properties)
{
// When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
if (mWpaSupplicant.interfacePath)
{
return;
}
mWpaSupplicant.interfacePath.reset(g_strdup(path));
if (mWpaSupplicant.interfacePath)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "WiFi interface added: %s",
mWpaSupplicant.interfacePath.get());
wpa_supplicant_1_interface_proxy_new_for_bus(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath.get(), nullptr,
reinterpret_cast<GAsyncReadyCallback>(+[](GObject * sourceObject_, GAsyncResult * res_, WpaSupplicantClient * self) {
return self->OnWpaInterfaceProxyReady(sourceObject_, res_);
}),
this);
}
}
void WpaSupplicantClient::OnWpaInterfaceScanDone(WpaSupplicant1Interface * iface, gboolean success)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "network scan done");
const char * const * bsss = wpa_supplicant_1_interface_get_bsss(iface);
if (bsss == nullptr)
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "no network found");
TEMPORARY_RETURN_IGNORED DeviceLayer::SystemLayer().ScheduleLambda(
[this]() { mConnectivityManagerImpl->OnScanFinished(Status::kSuccess, CharSpan(), nullptr); });
return;
}
auto networkScanned = std::make_unique<std::vector<WiFiScanResponse>>();
for (const char * bssPath = (bsss != nullptr ? *bsss : nullptr); bssPath != nullptr; bssPath = *(++bsss))
{
WiFiScanResponse network;
if (GetBssInfo(bssPath, network))
{
if (mInterestedSSID.empty() ||
(network.ssidLen == mInterestedSSID.size() &&
memcmp(network.ssid, mInterestedSSID.data(), mInterestedSSID.size()) == 0))
{
networkScanned->push_back(network);
}
}
}
CHIP_ERROR err = DeviceLayer::SystemLayer().ScheduleLambda([this, scanned = networkScanned.get()]() {
// Note: We cannot post an event in ScheduleLambda since std::vector is not trivial copyable.
LinuxScanResponseIterator<WiFiScanResponse> iter(scanned);
mConnectivityManagerImpl->OnScanFinished(Status::kSuccess, CharSpan(), &iter);
delete scanned;
});
if (::chip::ChipError::IsSuccess(err))
{
networkScanned.release();
}
else
{
ChipLogError(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "failed to schedule scan completion: %" CHIP_ERROR_FORMAT,
err.Format());
}
OnWiFiMediumAvailable(*this, true);
}
void WpaSupplicantClient::OnWpaInterfaceReady(GObject * sourceObject, GAsyncResult * res)
{
// When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
GAutoPtr<GError> err;
if (wpa_supplicant_1_call_get_interface_finish(mWpaSupplicant.proxy.get(), &mWpaSupplicant.interfacePath.GetReceiver(), res,
&err.GetReceiver()))
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "WiFi interface: %s", mWpaSupplicant.interfacePath.get());
wpa_supplicant_1_interface_proxy_new_for_bus(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath.get(), nullptr,
reinterpret_cast<GAsyncReadyCallback>(+[](GObject * sourceObject_, GAsyncResult * res_, WpaSupplicantClient * self) {
return self->OnWpaInterfaceProxyReady(sourceObject_, res_);
}),
this);
}
else
{
GVariant * args = nullptr;
GVariantBuilder builder;
const char * const ifname = (mWiFiIfName[0] != '\0') ? mWiFiIfName : CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME;
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "can't find interface %s: %s", ifname,
err ? err->message : "unknown error");
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "try to create interface %s", ifname);
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(ifname));
args = g_variant_builder_end(&builder);
err.reset();
if (wpa_supplicant_1_call_create_interface_sync(mWpaSupplicant.proxy.get(), args,
&mWpaSupplicant.interfacePath.GetReceiver(), nullptr, &err.GetReceiver()))
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "WiFi interface: %s", mWpaSupplicant.interfacePath.get());
Platform::CopyString(mWiFiIfName, ifname);
wpa_supplicant_1_interface_proxy_new_for_bus(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, mWpaSupplicant.interfacePath.get(), nullptr,
reinterpret_cast<GAsyncReadyCallback>(
+[](GObject * sourceObject_, GAsyncResult * res_, WpaSupplicantClient * self) {
return self->OnWpaInterfaceProxyReady(sourceObject_, res_);
}),
this);
}
else
{
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "failed to create interface %s: %s",
CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, err ? err->message : "unknown error");
mWpaSupplicant.interfacePath.reset();
}
}
}
void WpaSupplicantClient::NotifyWiFiConnectivityChange(const ConnectivityChange & inChange)
{
ChipDeviceEvent event{ .Type = DeviceEventType::kWiFiConnectivityChange, .WiFiConnectivityChange = { .Result = inChange } };
PlatformMgr().PostEventOrDie(&event);
}
CHIP_ERROR WpaSupplicantClient::StartOnGLib(void)
{
// When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
ChipLogProgress(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Start WiFi management");
wpa_supplicant_1_proxy_new_for_bus(
G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, kWpaSupplicantObjectPath, nullptr,
reinterpret_cast<GAsyncReadyCallback>(+[](GObject * sourceObject_, GAsyncResult * res_, WpaSupplicantClient * self) {
return self->OnWpaProxyReady(sourceObject_, res_);
}),
this);
return CHIP_NO_ERROR;
}
void WpaSupplicantClient::Start(void)
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
mWpaSupplicant = GDBusWpaSupplicant{};
CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](WpaSupplicantClient * self) { return self->StartOnGLib(); }, this);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(DeviceLayer, "Failed to start WiFi management"));
}
CHIP_ERROR WpaSupplicantClient::StartSync(void)
{
if (IsStarted())
{
return CHIP_NO_ERROR;
}
ChipLogProgress(DeviceLayer, "Start and sync Wi-Fi Management.");
static constexpr useconds_t kWiFiStartCheckTimeUsec = WIFI_START_CHECK_TIME_USEC;
static constexpr uint8_t kWiFiStartCheckAttempts = WIFI_START_CHECK_ATTEMPTS;
Start();
for (int cnt = 0; cnt < kWiFiStartCheckAttempts; cnt++)
{
if (IsStarted())
{
break;
}
usleep(kWiFiStartCheckTimeUsec);
}
if (!IsStarted())
{
ChipLogError(DeviceLayer, "Wi-Fi Management can't be started.");
return CHIP_ERROR_INTERNAL;
}
ChipLogProgress(DeviceLayer, "Wi-Fi Management is started");
return CHIP_NO_ERROR;
}
CHIP_ERROR WpaSupplicantClient::StopAutoScan(void)
{
std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
VerifyOrReturnError(mWpaSupplicant.iface, CHIP_ERROR_INCORRECT_STATE);
ChipLogDetail(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "disabling auto scan");
GAutoPtr<GError> err;
if (!wpa_supplicant_1_interface_call_auto_scan_sync(mWpaSupplicant.iface.get(), "" /* empty string means disabling auto scan */,
nullptr, &err.GetReceiver()))
{
ChipLogError(DeviceLayer, WPA_SUPPLICANT_CLIENT_LOG_PREFIX "Failed to stop auto network scan: %s",
err ? err->message : "unknown");
return CHIP_ERROR_INTERNAL;
}
return CHIP_NO_ERROR;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip