Full Network Commissioning Cluster w/ Linux Implementation (#12931)
* Linux network commissioning
* Rename network-commissioning -> network-commissioning-old in existing apps
* Update python test script
* Enable NetworkCommissioningCluster on Endpoint 0 and Endpoint 1
* Run Codegen
diff --git a/src/platform/Linux/BUILD.gn b/src/platform/Linux/BUILD.gn
index 8803940..f5bdc54 100644
--- a/src/platform/Linux/BUILD.gn
+++ b/src/platform/Linux/BUILD.gn
@@ -61,6 +61,9 @@
"KeyValueStoreManagerImpl.cpp",
"KeyValueStoreManagerImpl.h",
"Logging.cpp",
+ "NetworkCommissioningDriver.h",
+ "NetworkCommissioningThreadDriver.cpp",
+ "NetworkCommissioningWiFiDriver.cpp",
"PlatformManagerImpl.cpp",
"PlatformManagerImpl.h",
"PosixConfig.cpp",
@@ -81,6 +84,7 @@
deps = [ "${chip_root}/src/setup_payload" ]
public_deps = [
+ "${chip_root}/src/app/common:cluster-objects",
"${chip_root}/src/platform:platform_base",
"${chip_root}/third_party/inipp",
]
diff --git a/src/platform/Linux/ConnectivityManagerImpl.cpp b/src/platform/Linux/ConnectivityManagerImpl.cpp
index 8cc3fc1..1496f04 100644
--- a/src/platform/Linux/ConnectivityManagerImpl.cpp
+++ b/src/platform/Linux/ConnectivityManagerImpl.cpp
@@ -22,11 +22,15 @@
#include <platform/DiagnosticDataProvider.h>
#include <platform/Linux/ConnectivityUtils.h>
#include <platform/Linux/DiagnosticDataProviderImpl.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
#include <platform/Linux/WirelessDefs.h>
#include <platform/internal/BLEManager.h>
#include <cstdlib>
#include <new>
+#include <string>
+#include <utility>
+#include <vector>
#include <ifaddrs.h>
#include <stdio.h>
@@ -60,6 +64,8 @@
using namespace ::chip::app::Clusters::GeneralDiagnostics;
using namespace ::chip::app::Clusters::WiFiNetworkDiagnostics;
+using namespace ::chip::DeviceLayer::NetworkCommissioning;
+
namespace chip {
namespace DeviceLayer {
@@ -69,12 +75,17 @@
char ConnectivityManagerImpl::sWiFiIfName[];
#endif
+WiFiDriver::ScanCallback * ConnectivityManagerImpl::mpScanCallback;
+NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * ConnectivityManagerImpl::mpConnectCallback;
+
CHIP_ERROR ConnectivityManagerImpl::_Init()
{
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
mWiFiStationMode = kWiFiStationMode_Disabled;
mWiFiStationReconnectInterval = System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_WIFI_STATION_RECONNECT_INTERVAL);
#endif
+ mpConnectCallback = nullptr;
+ mpScanCallback = nullptr;
if (ConnectivityUtils::GetEthInterfaceName(mEthIfName, IFNAMSIZ) == CHIP_NO_ERROR)
{
@@ -427,6 +438,7 @@
ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant interface proxy");
g_signal_connect(mWpaSupplicant.iface, "properties-changed", G_CALLBACK(_OnWpaPropertiesChanged), NULL);
+ g_signal_connect(mWpaSupplicant.iface, "scan-done", G_CALLBACK(_OnWpaInterfaceScanDone), NULL);
}
else
{
@@ -632,6 +644,17 @@
mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED;
}
+ // We need to stop auto scan or it will block our network scan.
+ DeviceLayer::SystemLayer().ScheduleLambda([]() {
+ std::lock_guard<std::mutex> innerLock(mWpaSupplicantMutex);
+ ChipLogDetail(DeviceLayer, "Disabling auto scan");
+ CHIP_ERROR errInner = StopAutoScan();
+ if (errInner != CHIP_NO_ERROR)
+ {
+ ChipLogError(DeviceLayer, "Failed to stop auto scan");
+ }
+ });
+
if (err != nullptr)
g_error_free(err);
}
@@ -864,6 +887,126 @@
sInstance.DriveAPState();
}
+CHIP_ERROR
+ConnectivityManagerImpl::ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials,
+ NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * apCallback)
+{
+ CHIP_ERROR ret = CHIP_NO_ERROR;
+ GError * err = nullptr;
+ GVariant * args = nullptr;
+ GVariantBuilder builder;
+ gboolean result;
+ char ssidStr[kMaxWiFiSSIDLength] = { 0 };
+ char keyStr[kMaxWiFiKeyLength] = { 0 };
+ // There is another ongoing connect request, reject the new one.
+ VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+
+ // Clean up current network if exists
+ if (mWpaSupplicant.networkPath)
+ {
+ GError * error = nullptr;
+
+ result = wpa_fi_w1_wpa_supplicant1_interface_call_remove_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath,
+ nullptr, &error);
+
+ if (result)
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: removed network: %s", mWpaSupplicant.networkPath);
+ g_free(mWpaSupplicant.networkPath);
+ mWpaSupplicant.networkPath = nullptr;
+ }
+ else
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s",
+ error ? error->message : "unknown error");
+ ret = CHIP_ERROR_INTERNAL;
+ }
+
+ if (error != nullptr)
+ g_error_free(error);
+
+ SuccessOrExit(ret);
+ }
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+ memcpy(ssidStr, ssid.data(), ssid.size());
+ memcpy(keyStr, credentials.data(), credentials.size());
+ g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssidStr));
+ g_variant_builder_add(&builder, "{sv}", "psk", g_variant_new_string(keyStr));
+ g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("WPA-PSK"));
+ args = g_variant_builder_end(&builder);
+
+ result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync(mWpaSupplicant.iface, args, &mWpaSupplicant.networkPath,
+ nullptr, &err);
+
+ if (result)
+ {
+ // Note: wpa_supplicant will return immediately if the network is already connected, but it will still try reconnect in the
+ // background. The client still need to wait for a few seconds for this reconnect operation. So we always disconnect from
+ // the network we are connected and ignore any errors.
+ wpa_fi_w1_wpa_supplicant1_interface_call_disconnect_sync(mWpaSupplicant.iface, nullptr, nullptr);
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: %s", mWpaSupplicant.networkPath);
+
+ wpa_fi_w1_wpa_supplicant1_interface_call_select_network(mWpaSupplicant.iface, mWpaSupplicant.networkPath, nullptr,
+ _ConnectWiFiNetworkAsyncCallback, this);
+ mpConnectCallback = apCallback;
+ }
+ else
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s", err ? err->message : "unknown error");
+
+ if (mWpaSupplicant.networkPath)
+ {
+ g_object_unref(mWpaSupplicant.networkPath);
+ mWpaSupplicant.networkPath = nullptr;
+ }
+
+ ret = CHIP_ERROR_INTERNAL;
+ }
+
+exit:
+ if (err != nullptr)
+ g_error_free(err);
+
+ return ret;
+}
+
+void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+ ConnectivityManagerImpl * this_ = reinterpret_cast<ConnectivityManagerImpl *>(user_data);
+ std::unique_ptr<GVariant, GVariantDeleter> attachRes;
+ std::unique_ptr<GError, GErrorDeleter> err;
+ {
+ gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_finish(mWpaSupplicant.iface, res,
+ &MakeUniquePointerReceiver(err).Get());
+ if (!result)
+ {
+ ChipLogError(DeviceLayer, "Failed to perform connect network: %s", err == nullptr ? "unknown error" : err->message);
+ DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+ if (mpConnectCallback != nullptr)
+ {
+ // TODO: Replace this with actual thread attach result.
+ this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0);
+ this_->mpConnectCallback = nullptr;
+ }
+ mpConnectCallback = nullptr;
+ });
+ }
+ else
+ {
+ DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+ if (this_->mpConnectCallback != nullptr)
+ {
+ // TODO: Replace this with actual thread attach result.
+ this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0);
+ this_->mpConnectCallback = nullptr;
+ }
+ this_->PostNetworkConnect();
+ });
+ }
+ }
+}
+
CHIP_ERROR ConnectivityManagerImpl::ProvisionWiFiNetwork(const char * ssid, const char * key)
{
CHIP_ERROR ret = CHIP_NO_ERROR;
@@ -937,45 +1080,7 @@
if (gerror != nullptr)
g_error_free(gerror);
- // Iterate on the network interface to see if we already have beed assigned addresses.
- // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session.
- // This should be removed or find a better place once we depercate the rendezvous session.
- for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next())
- {
- char ifName[chip::Inet::InterfaceId::kMaxIfNameLength];
- if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) &&
- strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0)
- {
- chip::Inet::IPAddress addr;
- if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4())
- {
- ChipDeviceEvent event;
- event.Type = DeviceEventType::kInternetConnectivityChange;
- event.InternetConnectivityChange.IPv4 = kConnectivity_Established;
- event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange;
- addr.ToString(event.InternetConnectivityChange.address);
-
- ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName,
- event.InternetConnectivityChange.address);
-
- PlatformMgr().PostEventOrDie(&event);
- }
- }
- }
-
- // Run dhclient for IP on WiFi.
- // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this.
- char cmdBuffer[128];
- sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName);
- int dhclientSystemRet = system(cmdBuffer);
- if (dhclientSystemRet != 0)
- {
- ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet);
- }
- else
- {
- ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName);
- }
+ PostNetworkConnect();
// Return success as long as the device is connected to the network
ret = CHIP_NO_ERROR;
@@ -1011,6 +1116,69 @@
return ret;
}
+void ConnectivityManagerImpl::PostNetworkConnect()
+{
+ // Iterate on the network interface to see if we already have beed assigned addresses.
+ // The temporary hack for getting IP address change on linux for network provisioning in the rendezvous session.
+ // This should be removed or find a better place once we depercate the rendezvous session.
+ for (chip::Inet::InterfaceAddressIterator it; it.HasCurrent(); it.Next())
+ {
+ char ifName[chip::Inet::InterfaceId::kMaxIfNameLength];
+ if (it.IsUp() && CHIP_NO_ERROR == it.GetInterfaceName(ifName, sizeof(ifName)) &&
+ strncmp(ifName, sWiFiIfName, sizeof(ifName)) == 0)
+ {
+ chip::Inet::IPAddress addr;
+ if ((it.GetAddress(addr) == CHIP_NO_ERROR) && addr.IsIPv4())
+ {
+ ChipDeviceEvent event;
+ event.Type = DeviceEventType::kInternetConnectivityChange;
+ event.InternetConnectivityChange.IPv4 = kConnectivity_Established;
+ event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange;
+ addr.ToString(event.InternetConnectivityChange.address);
+
+ ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", ifName,
+ event.InternetConnectivityChange.address);
+
+ PlatformMgr().PostEventOrDie(&event);
+ }
+ }
+ }
+
+ // Run dhclient for IP on WiFi.
+ // TODO: The wifi can be managed by networkmanager on linux so we don't have to care about this.
+ char cmdBuffer[128];
+ sprintf(cmdBuffer, CHIP_DEVICE_CONFIG_LINUX_DHCPC_CMD, sWiFiIfName);
+ int dhclientSystemRet = system(cmdBuffer);
+ if (dhclientSystemRet != 0)
+ {
+ ChipLogError(DeviceLayer, "Failed to run dhclient, system() returns %d", dhclientSystemRet);
+ }
+ else
+ {
+ ChipLogProgress(DeviceLayer, "dhclient is running on the %s interface.", sWiFiIfName);
+ }
+}
+
+CHIP_ERROR ConnectivityManagerImpl::CommitConfig()
+{
+ gboolean result;
+ std::unique_ptr<GError, GErrorDeleter> err;
+
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to network");
+
+ result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr,
+ &MakeUniquePointerReceiver(err).Get());
+
+ if (!result)
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", err ? err->message : "unknown error");
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!");
+ return CHIP_NO_ERROR;
+}
+
CHIP_ERROR ConnectivityManagerImpl::GetWiFiBssId(ByteSpan & value)
{
CHIP_ERROR err = CHIP_ERROR_READ_FAILED;
@@ -1110,6 +1278,368 @@
return CHIP_NO_ERROR;
}
+CHIP_ERROR ConnectivityManagerImpl::GetConnectedNetwork(NetworkCommissioning::Network & network)
+{
+ std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
+ std::unique_ptr<GError, GErrorDeleter> err;
+
+ const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface);
+
+ std::unique_ptr<WpaFiW1Wpa_supplicant1Network, GObjectDeleter> networkInfo(
+ wpa_fi_w1_wpa_supplicant1_network_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE,
+ kWpaSupplicantServiceName, networkPath, nullptr,
+ &MakeUniquePointerReceiver(err).Get()));
+ if (networkInfo == nullptr)
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ network.connected = wpa_fi_w1_wpa_supplicant1_network_get_enabled(networkInfo.get());
+ GVariant * properties = wpa_fi_w1_wpa_supplicant1_network_get_properties(networkInfo.get());
+ GVariant * ssid = g_variant_lookup_value(properties, "ssid", nullptr);
+ gsize length;
+ const gchar * ssidStr = g_variant_get_string(ssid, &length);
+ if (length > sizeof(network.networkID))
+ {
+ return CHIP_ERROR_INTERNAL;
+ }
+ // TODO: wpa_supplicant will return ssid with quotes! We should have a better way to get the actual ssid in bytes.
+ ChipLogDetail(DeviceLayer, "Current connected network: %s", ssidStr);
+ memcpy(network.networkID, ssidStr + 1, length - 2);
+ network.networkIDLen = length - 2;
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ConnectivityManagerImpl::StopAutoScan()
+{
+ std::unique_ptr<GError, GErrorDeleter> err;
+
+ gboolean result;
+ result = wpa_fi_w1_wpa_supplicant1_interface_call_auto_scan_sync(
+ mWpaSupplicant.iface, "" /* empty string means disabling auto scan */, nullptr, &MakeUniquePointerReceiver(err).Get());
+ if (!result)
+ {
+ ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto network scan: %s", err ? err->message : "unknown");
+ return CHIP_ERROR_INTERNAL;
+ }
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ConnectivityManagerImpl::StartWiFiScan(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
+{
+ std::lock_guard<std::mutex> lock(mWpaSupplicantMutex);
+ VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE);
+ // There is another ongoing scan request, reject the new one.
+ VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+
+ CHIP_ERROR ret = CHIP_NO_ERROR;
+ GError * err = nullptr;
+ GVariant * args = nullptr;
+ GVariantBuilder builder;
+ gboolean result;
+
+ 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);
+
+ result = wpa_fi_w1_wpa_supplicant1_interface_call_scan_sync(mWpaSupplicant.iface, args, nullptr, &err);
+
+ if (result)
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: initialized network scan.");
+ mpScanCallback = callback;
+ }
+ else
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start network scan: %s", err ? err->message : "unknown error");
+ ret = CHIP_ERROR_INTERNAL;
+ }
+
+ if (err != nullptr)
+ {
+ g_error_free(err);
+ }
+ return ret;
+}
+
+namespace {
+// wpa_supplicant's scan results don't contains the channel infomation, so we need this lookup table for resolving the band and
+// channel infomation.
+std::pair<WiFiBand, uint16_t> GetBandAndChannelFromFrequency(uint32_t freq)
+{
+ std::pair<WiFiBand, uint16_t> ret = std::make_pair(WiFiBand::k2g4, 0);
+ 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
+
+bool ConnectivityManagerImpl::_GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result)
+{
+ std::unique_ptr<GError, GErrorDeleter> err;
+ std::unique_ptr<WpaFiW1Wpa_supplicant1BSS, GObjectDeleter> bss(
+ wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName,
+ bssPath, nullptr, &MakeUniquePointerReceiver(err).Get()));
+
+ if (bss == nullptr)
+ {
+ return false;
+ }
+
+ WpaFiW1Wpa_supplicant1BSSProxy * bssProxy = WPA_FI_W1_WPA_SUPPLICANT1_BSS_PROXY(bss.get());
+
+ std::unique_ptr<GVariant, GVariantDeleter> ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID"));
+ std::unique_ptr<GVariant, GVariantDeleter> bssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "BSSID"));
+
+ const guchar * ssidStr = nullptr;
+ const guchar * bssidBuf = nullptr;
+ char bssidStr[2 * 6 + 5 + 1] = { 0 };
+ gsize ssidLen = 0;
+ gsize bssidLen = 0;
+ gint16 signal = wpa_fi_w1_wpa_supplicant1_bss_get_signal(bss.get());
+ guint16 frequency = wpa_fi_w1_wpa_supplicant1_bss_get_frequency(bss.get());
+
+ ssidStr = reinterpret_cast<const guchar *>(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(guchar)));
+ bssidBuf = reinterpret_cast<const guchar *>(g_variant_get_fixed_array(bssid.get(), &bssidLen, sizeof(guchar)));
+
+ 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
+ {
+ bssidLen = 0;
+ ChipLogError(DeviceLayer, "Got a network with bssid not equals to 6");
+ }
+ ChipLogDetail(DeviceLayer, "Network Found: %.*s (%s) Signal:%" PRId16, int(ssidLen), ssidStr, bssidStr, signal);
+
+ // A flag for enterprise encryption option to avoid returning open for these networks by mistake
+ // 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 uint8_t kEAP = (1 << 7);
+
+ auto IsNetworkWPAPSK = [](GVariant * wpa) -> uint8_t {
+ if (wpa == nullptr)
+ {
+ return 0;
+ }
+
+ GVariant * keyMgmt = g_variant_lookup_value(wpa, "KeyMgmt", nullptr);
+ if (keyMgmt == nullptr)
+ {
+ return 0;
+ }
+ const gchar ** keyMgmts = g_variant_get_strv(keyMgmt, nullptr);
+ const gchar ** keyMgmtsForFree = keyMgmts;
+ uint8_t res = 0;
+ for (const gchar * keyMgmtVal = (keyMgmts != nullptr ? *keyMgmts : nullptr); keyMgmtVal != nullptr;
+ keyMgmtVal = *(++keyMgmts))
+ {
+ if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-none") == 0)
+ {
+ res |= (1 << 2); // SecurityType::WPA_PERSONAL
+ }
+ else if (g_strcasecmp(keyMgmtVal, "wpa-eap"))
+ {
+ res |= (kEAP);
+ }
+ }
+ g_variant_unref(keyMgmt);
+ g_free(keyMgmtsForFree);
+ return res;
+ };
+ auto IsNetworkWPA2PSK = [](GVariant * rsn) -> uint8_t {
+ if (rsn == nullptr)
+ {
+ return 0;
+ }
+ GVariant * keyMgmt = g_variant_lookup_value(rsn, "KeyMgmt", nullptr);
+ if (keyMgmt == nullptr)
+ {
+ return 0;
+ }
+ const gchar ** keyMgmts = g_variant_get_strv(keyMgmt, nullptr);
+ const gchar ** keyMgmtsForFree = keyMgmts;
+ uint8_t res = 0;
+ for (const gchar * keyMgmtVal = (keyMgmts != nullptr ? *keyMgmts : nullptr); keyMgmtVal != nullptr;
+ keyMgmtVal = *(++keyMgmts))
+ {
+ if (g_strcasecmp(keyMgmtVal, "wpa-psk") == 0 || g_strcasecmp(keyMgmtVal, "wpa-psk-sha256") == 0 ||
+ g_strcasecmp(keyMgmtVal, "wpa-ft-psk") == 0)
+ {
+ res |= (1 << 3); // SecurityType::WPA2_PERSONAL
+ }
+ else if (g_strcasecmp(keyMgmtVal, "wpa-eap") == 0 || g_strcasecmp(keyMgmtVal, "wpa-eap-sha256") == 0 ||
+ g_strcasecmp(keyMgmtVal, "wpa-ft-eap") == 0)
+ {
+ res |= 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 |= (1 << 4); // SecurityType::WPA3_PERSONAL
+ }
+ }
+ g_variant_unref(keyMgmt);
+ g_free(keyMgmtsForFree);
+ return res;
+ };
+ auto GetNetworkSecurityType = [IsNetworkWPAPSK, IsNetworkWPA2PSK](WpaFiW1Wpa_supplicant1BSSProxy * proxy) -> uint8_t {
+ std::unique_ptr<GVariant, GVariantDeleter> wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA"));
+ std::unique_ptr<GVariant, GVariantDeleter> rsn(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "RSN"));
+
+ uint8_t res = IsNetworkWPAPSK(wpa.get()) | IsNetworkWPA2PSK(rsn.get());
+ if (res == 0)
+ {
+ res = 1; // Open
+ }
+ return res & (0x7F);
+ };
+
+ // Drop the network if its SSID or BSSID is illegal.
+ VerifyOrReturnError(ssidLen <= kMaxWiFiSSIDLength, false);
+ VerifyOrReturnError(bssidLen == kWiFiBSSIDLength, false);
+ memcpy(result.ssid, ssidStr, ssidLen);
+ memcpy(result.bssid, bssidBuf, bssidLen);
+ result.ssidLen = ssidLen;
+ if (signal < INT8_MIN)
+ {
+ result.rssi = INT8_MIN;
+ }
+ else if (signal > INT8_MAX)
+ {
+ result.rssi = INT8_MAX;
+ }
+ else
+ {
+ result.rssi = static_cast<uint8_t>(signal);
+ }
+
+ auto bandInfo = GetBandAndChannelFromFrequency(frequency);
+ result.wiFiBand = bandInfo.first;
+ result.channel = bandInfo.second;
+ result.security = GetNetworkSecurityType(bssProxy);
+
+ return true;
+}
+
+void ConnectivityManagerImpl::_OnWpaInterfaceScanDone(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: network scan done");
+ gchar ** bsss = wpa_fi_w1_wpa_supplicant1_interface_dup_bsss(mWpaSupplicant.iface);
+ gchar ** oldBsss = bsss;
+ if (bsss == nullptr)
+ {
+ ChipLogProgress(DeviceLayer, "wpa_supplicant: no network found");
+ DeviceLayer::SystemLayer().ScheduleLambda([]() {
+ if (mpScanCallback != nullptr)
+ {
+ mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), nullptr);
+ mpScanCallback = nullptr;
+ }
+ });
+ return;
+ }
+
+ std::vector<WiFiScanResponse> * networkScanned = new std::vector<WiFiScanResponse>();
+ for (const gchar * bssPath = (bsss != nullptr ? *bsss : nullptr); bssPath != nullptr; bssPath = *(++bsss))
+ {
+ WiFiScanResponse network;
+ if (_GetBssInfo(bssPath, network))
+ {
+ networkScanned->push_back(network);
+ }
+ }
+
+ DeviceLayer::SystemLayer().ScheduleLambda([networkScanned]() {
+ // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of
+ // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this
+ // lambda.
+ if (mpScanCallback != nullptr)
+ {
+ LinuxScanResponseIterator<WiFiScanResponse> iter(const_cast<std::vector<WiFiScanResponse> *>(networkScanned));
+ mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter);
+ mpScanCallback = nullptr;
+ }
+
+ delete const_cast<std::vector<WiFiScanResponse> *>(networkScanned);
+ });
+
+ g_strfreev(oldBsss);
+}
+
#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
} // namespace DeviceLayer
diff --git a/src/platform/Linux/ConnectivityManagerImpl.h b/src/platform/Linux/ConnectivityManagerImpl.h
index 39b7aec..555aa2a 100644
--- a/src/platform/Linux/ConnectivityManagerImpl.h
+++ b/src/platform/Linux/ConnectivityManagerImpl.h
@@ -45,6 +45,10 @@
#include <mutex>
#endif
+#include <platform/Linux/NetworkCommissioningDriver.h>
+#include <platform/NetworkCommissioning.h>
+#include <vector>
+
namespace chip {
namespace Inet {
class IPAddress;
@@ -110,11 +114,19 @@
public:
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
CHIP_ERROR ProvisionWiFiNetwork(const char * ssid, const char * key);
+ CHIP_ERROR ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials,
+ NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback);
+ void PostNetworkConnect();
+ static void _ConnectWiFiNetworkAsyncCallback(GObject * source_object, GAsyncResult * res, gpointer user_data);
+ CHIP_ERROR CommitConfig();
+
void StartWiFiManagement();
bool IsWiFiManagementStarted();
CHIP_ERROR GetWiFiBssId(ByteSpan & value);
CHIP_ERROR GetWiFiSecurityType(uint8_t & securityType);
CHIP_ERROR GetWiFiVersion(uint8_t & wiFiVersion);
+ CHIP_ERROR GetConnectedNetwork(NetworkCommissioning::Network & network);
+ CHIP_ERROR StartWiFiScan(ByteSpan ssid, NetworkCommissioning::WiFiDriver::ScanCallback * callback);
#endif
const char * GetEthernetIfName() { return (mEthIfName[0] == '\0') ? nullptr : mEthIfName; }
@@ -126,6 +138,18 @@
private:
// ===== Members that implement the ConnectivityManager abstract interface.
+ struct WiFiNetworkScanned
+ {
+ // The fields matches WiFiInterfaceScanResult::Type.
+ uint8_t ssid[Internal::kMaxWiFiSSIDLength];
+ uint8_t ssidLen;
+ uint8_t bssid[6];
+ int8_t rssi;
+ uint16_t frequencyBand;
+ uint8_t channel;
+ uint8_t security;
+ };
+
CHIP_ERROR _Init();
void _OnPlatformEvent(const ChipDeviceEvent * event);
@@ -150,6 +174,7 @@
void _MaintainOnDemandWiFiAP();
System::Clock::Timeout _GetWiFiAPIdleTimeout();
void _SetWiFiAPIdleTimeout(System::Clock::Timeout val);
+ static CHIP_ERROR StopAutoScan();
static void _OnWpaProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
static void _OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const gchar * path, GVariant * properties,
@@ -160,6 +185,9 @@
static void _OnWpaInterfaceReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
static void _OnWpaInterfaceProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
static void _OnWpaBssProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data);
+ static void _OnWpaInterfaceScanDone(GObject * source_object, GAsyncResult * res, gpointer user_data);
+
+ static bool _GetBssInfo(const gchar * bssPath, NetworkCommissioning::WiFiScanResponse & result);
static bool mAssociattionStarted;
static BitFlags<ConnectivityFlags> mConnectivityFlag;
@@ -199,6 +227,9 @@
#if CHIP_DEVICE_CONFIG_ENABLE_WIFI
static char sWiFiIfName[IFNAMSIZ];
#endif
+
+ static NetworkCommissioning::WiFiDriver::ScanCallback * mpScanCallback;
+ static NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback;
};
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
diff --git a/src/platform/Linux/NetworkCommissioningDriver.h b/src/platform/Linux/NetworkCommissioningDriver.h
new file mode 100644
index 0000000..41daa19
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningDriver.h
@@ -0,0 +1,159 @@
+/*
+ *
+ * Copyright (c) 2021 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.
+ */
+
+#pragma once
+
+#include <platform/NetworkCommissioning.h>
+#include <vector>
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+template <typename T>
+class LinuxScanResponseIterator : public Iterator<T>
+{
+public:
+ LinuxScanResponseIterator(std::vector<T> * apScanResponse) : mpScanResponse(apScanResponse) {}
+ size_t Count() override { return mpScanResponse != nullptr ? mpScanResponse->size() : 0; }
+ bool Next(T & item) override
+ {
+ if (mpScanResponse == nullptr || currentIterating >= mpScanResponse->size())
+ {
+ return false;
+ }
+ item = (*mpScanResponse)[currentIterating];
+ currentIterating++;
+ return true;
+ }
+ void Release() override
+ { /* nothing to do, we don't hold the ownership of the vector, and users is not expected to hold the ownership in OnFinished for
+ scan. */
+ }
+
+private:
+ size_t currentIterating = 0;
+ // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable.
+ std::vector<T> * mpScanResponse;
+};
+
+#if CHIP_DEVICE_CONFIG_ENABLE_WPA
+class LinuxWiFiDriver final : public WiFiDriver
+{
+public:
+ class WiFiNetworkIterator final : public NetworkIterator
+ {
+ public:
+ WiFiNetworkIterator(LinuxWiFiDriver * aDriver) : driver(aDriver) {}
+ size_t Count() override;
+ bool Next(Network & item) override;
+ void Release() override { delete this; }
+ ~WiFiNetworkIterator() = default;
+
+ private:
+ LinuxWiFiDriver * driver;
+ bool exhausted = false;
+ };
+
+ struct WiFiNetwork
+ {
+ uint8_t ssid[DeviceLayer::Internal::kMaxWiFiSSIDLength];
+ uint8_t ssidLen = 0;
+ uint8_t credentials[DeviceLayer::Internal::kMaxWiFiKeyLength];
+ uint8_t credentialsLen = 0;
+ };
+
+ // BaseDriver
+ NetworkIterator * GetNetworks() override { return new WiFiNetworkIterator(this); }
+ CHIP_ERROR Init() override;
+ CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } // Nothing to do on linux for shutdown.
+
+ // WirelessDriver
+ uint8_t GetMaxNetworks() override { return 1; }
+ uint8_t GetScanNetworkTimeoutSeconds() override { return 10; }
+ uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; }
+
+ CHIP_ERROR CommitConfiguration() override;
+ CHIP_ERROR RevertConfiguration() override;
+
+ Status RemoveNetwork(ByteSpan networkId) override;
+ Status ReorderNetwork(ByteSpan networkId, uint8_t index) override;
+ void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override;
+
+ // WiFiDriver
+ Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials) override;
+ void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override;
+
+private:
+ bool NetworkMatch(const WiFiNetwork & network, ByteSpan networkId);
+
+ WiFiNetworkIterator mWiFiIterator = WiFiNetworkIterator(this);
+ WiFiNetwork mSavedNetwork;
+ WiFiNetwork mStagingNetwork;
+};
+#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
+
+#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
+class LinuxThreadDriver final : public ThreadDriver
+{
+public:
+ class ThreadNetworkIterator final : public NetworkIterator
+ {
+ public:
+ ThreadNetworkIterator(LinuxThreadDriver * aDriver) : driver(aDriver) {}
+ size_t Count() override;
+ bool Next(Network & item) override;
+ void Release() override { delete this; }
+ ~ThreadNetworkIterator() = default;
+
+ private:
+ LinuxThreadDriver * driver;
+ bool exhausted = false;
+ };
+
+ // BaseDriver
+ NetworkIterator * GetNetworks() override { return new ThreadNetworkIterator(this); }
+ CHIP_ERROR Init() override;
+ CHIP_ERROR Shutdown() override { return CHIP_NO_ERROR; } // Nothing to do on linux for shutdown.
+
+ // WirelessDriver
+ uint8_t GetMaxNetworks() override { return 1; }
+ uint8_t GetScanNetworkTimeoutSeconds() override { return 10; }
+ uint8_t GetConnectNetworkTimeoutSeconds() override { return 20; }
+
+ CHIP_ERROR CommitConfiguration() override;
+ CHIP_ERROR RevertConfiguration() override;
+
+ Status RemoveNetwork(ByteSpan networkId) override;
+ Status ReorderNetwork(ByteSpan networkId, uint8_t index) override;
+ void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override;
+
+ // ThreadDriver
+ Status AddOrUpdateNetwork(ByteSpan operationalDataset) override;
+ void ScanNetworks(ScanCallback * callback) override;
+
+private:
+ ThreadNetworkIterator mThreadIterator = ThreadNetworkIterator(this);
+ Thread::OperationalDataset mSavedNetwork;
+ Thread::OperationalDataset mStagingNetwork;
+};
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/NetworkCommissioningThreadDriver.cpp b/src/platform/Linux/NetworkCommissioningThreadDriver.cpp
new file mode 100644
index 0000000..abbeeaa
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningThreadDriver.cpp
@@ -0,0 +1,196 @@
+/*
+ *
+ * Copyright (c) 2021 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 <lib/support/CodeUtils.h>
+#include <lib/support/SafeInt.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
+#include <platform/Linux/ThreadStackManagerImpl.h>
+#include <platform/ThreadStackManager.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+using namespace chip;
+using namespace chip::Thread;
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+#if CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+// NOTE: For ThreadDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
+// load the network config from otbr-agent, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
+// all changed are made on the staging network.
+// TODO: The otbr-posix does not actually maintains its own networking states, it will always persist the last network connected.
+// This should not be an issue for most cases, but we should implement the code for maintaining the states by ourselves.
+
+CHIP_ERROR LinuxThreadDriver::Init()
+{
+ ByteSpan currentProvision;
+ VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), CHIP_NO_ERROR);
+ VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentProvision) == CHIP_NO_ERROR, CHIP_NO_ERROR);
+
+ mSavedNetwork.Init(currentProvision);
+ mStagingNetwork.Init(currentProvision);
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxThreadDriver::CommitConfiguration()
+{
+ // Note: otbr-agent will persist the networks by their own, we don't have much to do for saving the networks (see Init() above,
+ // we just loads the saved dataset from otbr-agent.)
+ mSavedNetwork = mStagingNetwork;
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxThreadDriver::RevertConfiguration()
+{
+ mStagingNetwork = mSavedNetwork;
+ return CHIP_NO_ERROR;
+}
+
+Status LinuxThreadDriver::AddOrUpdateNetwork(ByteSpan operationalDataset)
+{
+ uint8_t extpanid[kSizeExtendedPanId];
+ uint8_t newExtpanid[kSizeExtendedPanId];
+ Thread::OperationalDataset newDataset;
+
+ newDataset.Init(operationalDataset);
+ VerifyOrReturnError(newDataset.IsCommissioned(), Status::kOutOfRange);
+
+ VerifyOrReturnError(!mStagingNetwork.IsCommissioned() || memcmp(extpanid, newExtpanid, kSizeExtendedPanId) == 0,
+ Status::kBoundsExceeded);
+
+ mStagingNetwork = newDataset;
+ return Status::kSuccess;
+}
+
+Status LinuxThreadDriver::RemoveNetwork(ByteSpan networkId)
+{
+ uint8_t extpanid[kSizeExtendedPanId];
+ if (!mStagingNetwork.IsCommissioned())
+ {
+ return Status::kNetworkNotFound;
+ }
+ else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+ {
+ return Status::kUnknownError;
+ }
+
+ VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0,
+ Status::kNetworkNotFound);
+ mStagingNetwork.Clear();
+ return Status::kSuccess;
+}
+
+Status LinuxThreadDriver::ReorderNetwork(ByteSpan networkId, uint8_t index)
+{
+ uint8_t extpanid[kSizeExtendedPanId];
+ if (!mStagingNetwork.IsCommissioned())
+ {
+ return Status::kNetworkNotFound;
+ }
+ else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+ {
+ return Status::kUnknownError;
+ }
+
+ VerifyOrReturnError(networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0,
+ Status::kNetworkNotFound);
+
+ return Status::kSuccess;
+}
+
+void LinuxThreadDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
+{
+ NetworkCommissioning::Status status = Status::kSuccess;
+ uint8_t extpanid[kSizeExtendedPanId];
+ if (!mStagingNetwork.IsCommissioned())
+ {
+ ExitNow(status = Status::kNetworkNotFound);
+ }
+ else if (mStagingNetwork.GetExtendedPanId(extpanid) != CHIP_NO_ERROR)
+ {
+ ExitNow(status = Status::kUnknownError);
+ }
+
+ VerifyOrExit((networkId.size() == kSizeExtendedPanId && memcmp(networkId.data(), extpanid, kSizeExtendedPanId) == 0),
+ status = Status::kNetworkNotFound);
+
+ VerifyOrExit(DeviceLayer::ThreadStackMgrImpl().AttachToThreadNetwork(mStagingNetwork.AsByteSpan(), callback) == CHIP_NO_ERROR,
+ status = Status::kUnknownError);
+
+exit:
+ if (status != Status::kSuccess)
+ {
+ callback->OnResult(status, CharSpan(), 0);
+ }
+}
+
+void LinuxThreadDriver::ScanNetworks(ThreadDriver::ScanCallback * callback)
+{
+ CHIP_ERROR err = DeviceLayer::ThreadStackMgrImpl().StartThreadScan(callback);
+ if (err != CHIP_NO_ERROR)
+ {
+ callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
+ }
+}
+
+size_t LinuxThreadDriver::ThreadNetworkIterator::Count()
+{
+ return driver->mStagingNetwork.IsCommissioned() ? 1 : 0;
+}
+
+bool LinuxThreadDriver::ThreadNetworkIterator::Next(Network & item)
+{
+ if (exhausted || !driver->mStagingNetwork.IsCommissioned())
+ {
+ return false;
+ }
+ uint8_t extpanid[kSizeExtendedPanId];
+ VerifyOrReturnError(driver->mStagingNetwork.GetExtendedPanId(extpanid) == CHIP_NO_ERROR, false);
+ memcpy(item.networkID, extpanid, kSizeExtendedPanId);
+ item.networkIDLen = kSizeExtendedPanId;
+ item.connected = false;
+ exhausted = true;
+
+ ByteSpan currentProvision;
+ Thread::OperationalDataset currentDataset;
+ uint8_t enabledExtPanId[Thread::kSizeExtendedPanId];
+
+ // The Thread network is not actually enabled.
+ VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), true);
+ VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentProvision) == CHIP_NO_ERROR, true);
+ VerifyOrReturnError(currentDataset.Init(currentProvision) == CHIP_NO_ERROR, true);
+ // The Thread network is not enabled, but has a different extended pan id.
+ VerifyOrReturnError(currentDataset.GetExtendedPanId(enabledExtPanId) == CHIP_NO_ERROR, true);
+ VerifyOrReturnError(memcmp(extpanid, enabledExtPanId, kSizeExtendedPanId) == 0, true);
+ // The Thread network is enabled and has the same extended pan id as the one in our record.
+ item.connected = true;
+
+ return true;
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp b/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp
new file mode 100644
index 0000000..5483715
--- /dev/null
+++ b/src/platform/Linux/NetworkCommissioningWiFiDriver.cpp
@@ -0,0 +1,207 @@
+/*
+ *
+ * Copyright (c) 2021 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 <lib/support/CodeUtils.h>
+#include <lib/support/SafeInt.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+using namespace chip;
+using namespace chip::Thread;
+
+namespace chip {
+namespace DeviceLayer {
+namespace NetworkCommissioning {
+
+#if CHIP_DEVICE_CONFIG_ENABLE_WPA
+// TODO: Here, most interfaces are just calling ConnectivityManager interfaces, this is because the ConnectivityProvides some
+// bootstrap code for the wpa_supplicant. However, we can wrap the wpa_supplicant dbus api directly (and remove the related code in
+// ConnectivityManagerImpl).
+namespace {
+constexpr char kWiFiSSIDKeyName[] = "wifi-ssid";
+constexpr char kWiFiCredentialsKeyName[] = "wifi-pass";
+} // namespace
+
+// NOTE: For WiFiDriver, we uses two network configs, one is mSavedNetwork, and another is mStagingNetwork, during init, it will
+// load the network config from k-v storage, and loads it into both mSavedNetwork and mStagingNetwork. When updating the networks,
+// all changed are made on the staging network, and when the network is committed, it will update the mSavedNetwork to
+// mStagingNetwork and persist the changes.
+
+// NOTE: LinuxWiFiDriver uses network config with empty ssid (ssidLen = 0) for empty network config.
+
+// NOTE: For now, the LinuxWiFiDriver only supports one network, this can be fixed by using the wpa_supplicant API directly (then
+// wpa_supplicant will manage the networks for us.)
+
+CHIP_ERROR LinuxWiFiDriver::Init()
+{
+ CHIP_ERROR err;
+ size_t ssidLen = 0;
+ size_t credentialsLen = 0;
+
+ err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiCredentialsKeyName, mSavedNetwork.credentials,
+ sizeof(mSavedNetwork.credentials), &credentialsLen);
+ if (err == CHIP_ERROR_KEY_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+
+ err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiSSIDKeyName, mSavedNetwork.ssid, sizeof(mSavedNetwork.ssid), &ssidLen);
+ if (err == CHIP_ERROR_KEY_NOT_FOUND)
+ {
+ return CHIP_NO_ERROR;
+ }
+
+ mSavedNetwork.credentialsLen = credentialsLen;
+ mSavedNetwork.ssidLen = ssidLen;
+
+ mStagingNetwork = mSavedNetwork;
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxWiFiDriver::CommitConfiguration()
+{
+ ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiSSIDKeyName, mStagingNetwork.ssid, mStagingNetwork.ssidLen));
+ ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Put(kWiFiCredentialsKeyName, mStagingNetwork.credentials,
+ mStagingNetwork.credentialsLen));
+ ReturnErrorOnFailure(ConnectivityMgrImpl().CommitConfig());
+ mSavedNetwork = mStagingNetwork;
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR LinuxWiFiDriver::RevertConfiguration()
+{
+ mStagingNetwork = mSavedNetwork;
+ return CHIP_NO_ERROR;
+}
+
+bool LinuxWiFiDriver::NetworkMatch(const WiFiNetwork & network, ByteSpan networkId)
+{
+ return networkId.size() == network.ssidLen && memcmp(networkId.data(), network.ssid, network.ssidLen) == 0;
+}
+
+Status LinuxWiFiDriver::AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials)
+{
+ VerifyOrReturnError(mStagingNetwork.ssidLen == 0 || NetworkMatch(mStagingNetwork, ssid), Status::kBoundsExceeded);
+
+ static_assert(sizeof(WiFiNetwork::ssid) <= std::numeric_limits<decltype(WiFiNetwork::ssidLen)>::max(),
+ "Max length of WiFi ssid exceeds the limit of ssidLen field");
+ static_assert(sizeof(WiFiNetwork::credentials) <= std::numeric_limits<decltype(WiFiNetwork::credentialsLen)>::max(),
+ "Max length of WiFi credentials exceeds the limit of credentialsLen field");
+
+ // Do the check before setting the values, so the data is not updated on error.
+ VerifyOrReturnError(credentials.size() <= sizeof(mStagingNetwork.credentials), Status::kOutOfRange);
+ VerifyOrReturnError(ssid.size() <= sizeof(mStagingNetwork.ssid), Status::kOutOfRange);
+
+ memcpy(mStagingNetwork.credentials, credentials.data(), credentials.size());
+ mStagingNetwork.credentialsLen = static_cast<decltype(mStagingNetwork.credentialsLen)>(credentials.size());
+
+ memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size());
+ mStagingNetwork.ssidLen = static_cast<decltype(mStagingNetwork.ssidLen)>(ssid.size());
+
+ return Status::kSuccess;
+}
+
+Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId)
+{
+ VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
+
+ // Use empty ssid for representing invalid network
+ mStagingNetwork.ssidLen = 0;
+ return Status::kSuccess;
+}
+
+Status LinuxWiFiDriver::ReorderNetwork(ByteSpan networkId, uint8_t index)
+{
+ VerifyOrReturnError(NetworkMatch(mStagingNetwork, networkId), Status::kNetworkIDNotFound);
+ // We only support one network, so reorder is actually no-op.
+
+ return Status::kSuccess;
+}
+
+void LinuxWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callback)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ Status networkingStatus = Status::kSuccess;
+
+ VerifyOrExit(NetworkMatch(mStagingNetwork, networkId), networkingStatus = Status::kNetworkIDNotFound);
+
+ ChipLogProgress(NetworkProvisioning, "LinuxNetworkCommissioningDelegate: SSID: %s", networkId.data());
+
+ err = ConnectivityMgrImpl().ConnectWiFiNetworkAsync(ByteSpan(mStagingNetwork.ssid, mStagingNetwork.ssidLen),
+ ByteSpan(mStagingNetwork.credentials, mStagingNetwork.credentialsLen),
+ callback);
+exit:
+ if (err != CHIP_NO_ERROR)
+ {
+ networkingStatus = Status::kUnknownError;
+ }
+
+ if (networkingStatus != Status::kSuccess)
+ {
+ ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %s", chip::ErrorStr(err));
+ callback->OnResult(networkingStatus, CharSpan(), 0);
+ }
+}
+
+void LinuxWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callback)
+{
+ CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().StartWiFiScan(ssid, callback);
+ if (err != CHIP_NO_ERROR)
+ {
+ callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr);
+ }
+}
+
+size_t LinuxWiFiDriver::WiFiNetworkIterator::Count()
+{
+ return driver->mStagingNetwork.ssidLen == 0 ? 0 : 1;
+}
+
+bool LinuxWiFiDriver::WiFiNetworkIterator::Next(Network & item)
+{
+ if (exhausted || driver->mStagingNetwork.ssidLen == 0)
+ {
+ return false;
+ }
+ memcpy(item.networkID, driver->mStagingNetwork.ssid, driver->mStagingNetwork.ssidLen);
+ item.networkIDLen = driver->mStagingNetwork.ssidLen;
+ item.connected = false;
+ exhausted = true;
+
+ Network connectedNetwork;
+ CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConnectedNetwork(connectedNetwork);
+ if (err == CHIP_NO_ERROR)
+ {
+ if (connectedNetwork.networkIDLen == item.networkIDLen &&
+ memcmp(connectedNetwork.networkID, item.networkID, item.networkIDLen) == 0)
+ {
+ item.connected = true;
+ }
+ }
+
+ return true;
+}
+
+#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA
+
+} // namespace NetworkCommissioning
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/Linux/ThreadStackManagerImpl.cpp b/src/platform/Linux/ThreadStackManagerImpl.cpp
index 2fc6614..d50aed7 100644
--- a/src/platform/Linux/ThreadStackManagerImpl.cpp
+++ b/src/platform/Linux/ThreadStackManagerImpl.cpp
@@ -21,11 +21,17 @@
#include <app/AttributeAccessInterface.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
+#include <platform/Linux/NetworkCommissioningDriver.h>
#include <platform/PlatformManager.h>
#include <platform/ThreadStackManager.h>
+#include <nlbyteorder.hpp>
+#include <nlio-byteorder.hpp>
+#include <nlio.hpp>
+
using namespace ::chip::app;
using namespace ::chip::app::Clusters;
+using namespace chip::DeviceLayer::NetworkCommissioning;
namespace chip {
namespace DeviceLayer {
@@ -232,8 +238,15 @@
VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE);
{
+ // TODO: The following code does not works actually, since otbr-posix does not emit signals for properties changes. Which is
+ // required for gdbus to caching properties.
std::unique_ptr<GVariant, GVariantDeleter> value(
openthread_io_openthread_border_router_dup_active_dataset_tlvs(mProxy.get()));
+ if (value == nullptr)
+ {
+ netInfo = ByteSpan();
+ return CHIP_ERROR_KEY_NOT_FOUND;
+ }
GBytes * bytes = g_variant_get_data_as_bytes(value.get());
gsize size;
const uint8_t * data = reinterpret_cast<const uint8_t *>(g_bytes_get_data(bytes, &size));
@@ -276,20 +289,7 @@
VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE);
if (val)
{
- std::unique_ptr<GError, GErrorDeleter> err;
- gboolean result =
- openthread_io_openthread_border_router_call_attach_sync(mProxy.get(), nullptr, &MakeUniquePointerReceiver(err).Get());
- if (err)
- {
- ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Attach", err->message);
- return CHIP_ERROR_INTERNAL;
- }
-
- if (!result)
- {
- ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Attach", "return false");
- return CHIP_ERROR_INTERNAL;
- }
+ openthread_io_openthread_border_router_call_attach(mProxy.get(), nullptr, _OnThreadAttachFinished, this);
}
else
{
@@ -311,6 +311,41 @@
return CHIP_NO_ERROR;
}
+void ThreadStackManagerImpl::_OnThreadAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+ ThreadStackManagerImpl * this_ = reinterpret_cast<ThreadStackManagerImpl *>(user_data);
+ std::unique_ptr<GVariant, GVariantDeleter> attachRes;
+ std::unique_ptr<GError, GErrorDeleter> err;
+ {
+ gboolean result = openthread_io_openthread_border_router_call_attach_finish(this_->mProxy.get(), res,
+ &MakeUniquePointerReceiver(err).Get());
+ if (!result)
+ {
+ ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s",
+ err == nullptr ? "unknown error" : err->message);
+ DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+ if (this_->mpConnectCallback != nullptr)
+ {
+ // TODO: Replace this with actual thread attach result.
+ this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kUnknownError, CharSpan(), 0);
+ this_->mpConnectCallback = nullptr;
+ }
+ });
+ }
+ else
+ {
+ DeviceLayer::SystemLayer().ScheduleLambda([this_]() {
+ if (this_->mpConnectCallback != nullptr)
+ {
+ // TODO: Replace this with actual thread attach result.
+ this_->mpConnectCallback->OnResult(NetworkCommissioning::Status::kSuccess, CharSpan(), 0);
+ this_->mpConnectCallback = nullptr;
+ }
+ });
+ }
+ }
+}
+
ConnectivityManager::ThreadDeviceType ThreadStackManagerImpl::_GetThreadDeviceType()
{
ConnectivityManager::ThreadDeviceType type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported;
@@ -472,6 +507,109 @@
return CHIP_ERROR_NOT_IMPLEMENTED;
}
+CHIP_ERROR ThreadStackManagerImpl::StartThreadScan(ThreadDriver::ScanCallback * callback)
+{
+ // There is another ongoing scan request, reject the new one.
+ VerifyOrReturnError(mpScanCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+ mpScanCallback = callback;
+ openthread_io_openthread_border_router_call_scan(mProxy.get(), nullptr, _OnNetworkScanFinished, this);
+ return CHIP_NO_ERROR;
+}
+
+void ThreadStackManagerImpl::_OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data)
+{
+ ThreadStackManagerImpl * this_ = reinterpret_cast<ThreadStackManagerImpl *>(user_data);
+ this_->_OnNetworkScanFinished(res);
+}
+
+void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res)
+{
+ std::unique_ptr<GVariant, GVariantDeleter> scan_result;
+ std::unique_ptr<GError, GErrorDeleter> err;
+ {
+ gboolean result = openthread_io_openthread_border_router_call_scan_finish(
+ mProxy.get(), &MakeUniquePointerReceiver(scan_result).Get(), res, &MakeUniquePointerReceiver(err).Get());
+ if (!result)
+ {
+ ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s",
+ err == nullptr ? "unknown error" : err->message);
+ DeviceLayer::SystemLayer().ScheduleLambda([this]() {
+ if (mpScanCallback != nullptr)
+ {
+ LinuxScanResponseIterator<ThreadScanResponse> iter(nullptr);
+ mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter);
+ }
+ mpScanCallback = nullptr;
+ });
+ }
+ }
+
+ std::vector<NetworkCommissioning::ThreadScanResponse> * scanResult =
+ new std::vector<NetworkCommissioning::ThreadScanResponse>();
+
+ if (g_variant_n_children(scan_result.get()) > 0)
+ {
+ std::unique_ptr<GVariantIter, GVariantIterDeleter> iter;
+ g_variant_get(scan_result.get(), "a(tstayqqyyyybb)", &MakeUniquePointerReceiver(iter).Get());
+ if (!iter)
+ return;
+
+ guint64 ext_address;
+ const gchar * network_name;
+ guint64 ext_panid;
+ const gchar * steering_data;
+ guint16 panid;
+ guint16 joiner_udp_port;
+ guint8 channel;
+ guint8 rssi;
+ guint8 lqi;
+ guint8 version;
+ gboolean is_native;
+ gboolean is_joinable;
+
+ while (g_variant_iter_loop(iter.get(), "(tstayqqyyyybb)", &ext_address, &network_name, &ext_panid, &steering_data, &panid,
+ &joiner_udp_port, &channel, &rssi, &lqi, &version, &is_native, &is_joinable))
+ {
+ ChipLogProgress(DeviceLayer,
+ "Thread Network: %s (%016" PRIx64 ") ExtPanId(%016" PRIx64 ") RSSI %" PRIu16 " LQI %" PRIu8
+ " Version %" PRIu8,
+ network_name, ext_address, ext_panid, rssi, lqi, version);
+ NetworkCommissioning::ThreadScanResponse networkScanned;
+ networkScanned.panId = panid;
+ networkScanned.extendedPanId = ext_panid;
+ size_t networkNameLen = strlen(network_name);
+ if (networkNameLen > 16)
+ {
+ ChipLogProgress(DeviceLayer, "Network name is too long, ignore it.");
+ continue;
+ }
+ networkScanned.networkNameLen = static_cast<uint8_t>(networkNameLen);
+ memcpy(networkScanned.networkName, network_name, networkNameLen);
+ networkScanned.channel = channel;
+ networkScanned.version = version;
+ networkScanned.extendedAddress = 0;
+ networkScanned.rssi = rssi;
+ networkScanned.lqi = lqi;
+
+ scanResult->push_back(networkScanned);
+ }
+ }
+
+ DeviceLayer::SystemLayer().ScheduleLambda([this, scanResult]() {
+ // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of
+ // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this
+ // lambda.
+ if (mpScanCallback != nullptr)
+ {
+ LinuxScanResponseIterator<NetworkCommissioning::ThreadScanResponse> iter(
+ const_cast<std::vector<ThreadScanResponse> *>(scanResult));
+ mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter);
+ mpScanCallback = nullptr;
+ }
+ delete const_cast<std::vector<ThreadScanResponse> *>(scanResult);
+ });
+}
+
void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {}
CHIP_ERROR ThreadStackManagerImpl::_WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId,
@@ -498,6 +636,19 @@
return err;
}
+CHIP_ERROR
+ThreadStackManagerImpl::AttachToThreadNetwork(ByteSpan netInfo,
+ NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback)
+{
+ // There is another ongoing connect request, reject the new one.
+ VerifyOrReturnError(mpConnectCallback == nullptr, CHIP_ERROR_INCORRECT_STATE);
+ ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(false));
+ ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadProvision(netInfo));
+ ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(true));
+ mpConnectCallback = callback;
+ return CHIP_NO_ERROR;
+}
+
ThreadStackManager & ThreadStackMgr()
{
return chip::DeviceLayer::ThreadStackManagerImpl::sInstance;
diff --git a/src/platform/Linux/ThreadStackManagerImpl.h b/src/platform/Linux/ThreadStackManagerImpl.h
index 3bbd298..caac537 100644
--- a/src/platform/Linux/ThreadStackManagerImpl.h
+++ b/src/platform/Linux/ThreadStackManagerImpl.h
@@ -18,11 +18,13 @@
#pragma once
#include <memory>
+#include <vector>
#include <app/AttributeAccessInterface.h>
#include <lib/support/ThreadOperationalDataset.h>
#include <platform/Linux/GlibTypeDeleter.h>
#include <platform/Linux/dbus/openthread/introspect.h>
+#include <platform/NetworkCommissioning.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include <platform/internal/DeviceNetworkInfo.h>
@@ -50,6 +52,11 @@
CHIP_ERROR _SetThreadProvision(ByteSpan netInfo);
+ void _OnNetworkScanFinished(GAsyncResult * res);
+ static void _OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data);
+
+ CHIP_ERROR GetExtendedPanId(uint8_t extPanId[Thread::kSizeExtendedPanId]);
+
void _ErasePersistentInfo();
bool _IsThreadProvisioned();
@@ -58,7 +65,10 @@
bool _IsThreadAttached();
+ CHIP_ERROR AttachToThreadNetwork(ByteSpan netInfo, NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback);
+
CHIP_ERROR _SetThreadEnabled(bool val);
+ static void _OnThreadAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data);
ConnectivityManager::ThreadDeviceType _GetThreadDeviceType();
@@ -90,6 +100,8 @@
CHIP_ERROR _WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId, app::AttributeValueEncoder & encoder);
+ CHIP_ERROR StartThreadScan(NetworkCommissioning::ThreadDriver::ScanCallback * callback);
+
~ThreadStackManagerImpl() = default;
static ThreadStackManagerImpl sInstance;
@@ -106,6 +118,19 @@
static constexpr char kPropertyDeviceRole[] = "DeviceRole";
+ struct ThreadNetworkScanned
+ {
+ uint16_t panId;
+ uint64_t extendedPanId;
+ uint8_t networkName[16];
+ uint8_t networkNameLen;
+ uint16_t channel;
+ uint8_t version;
+ uint64_t extendedAddress;
+ int8_t rssi;
+ uint8_t lqi;
+ };
+
std::unique_ptr<OpenthreadIoOpenthreadBorderRouter, GObjectDeleter> mProxy;
static void OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties,
@@ -114,6 +139,9 @@
Thread::OperationalDataset mDataset = {};
+ NetworkCommissioning::ThreadDriver::ScanCallback * mpScanCallback;
+ NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback;
+
bool mAttached;
};
diff --git a/src/platform/Linux/dbus/openthread/introspect.xml b/src/platform/Linux/dbus/openthread/introspect.xml
index e92b3ac..e583697 100644
--- a/src/platform/Linux/dbus/openthread/introspect.xml
+++ b/src/platform/Linux/dbus/openthread/introspect.xml
@@ -30,8 +30,8 @@
uint8[] steering_data
uint16 panid
uint16 joiner_udp_port
- uint16 channel
- uint16 rssi
+ uint8 channel
+ uint8 rssi
uint8 lqi
uint8 version
bool is_native
@@ -40,7 +40,7 @@
</literallayout>
-->
<method name="Scan">
- <arg name="scan_result" type="a(tstayqqqqyybb)" direction="out"/>
+ <arg name="scan_result" type="a(tstayqqyyyybb)" direction="out"/>
</method>
<!-- Attach: Attach the current device to the Thread network using the current active network dataset. -->
diff --git a/src/platform/Linux/dbus/wpa/DBusWpaBss.xml b/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
index b6e8fee..8ec5c4a 100644
--- a/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
+++ b/src/platform/Linux/dbus/wpa/DBusWpaBss.xml
@@ -2,8 +2,18 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "https://raw.githubusercontent.com/freedesktop/dbus/master/doc/introspect.dtd">
<node>
<interface name="fi.w1.wpa_supplicant1.BSS">
- <property name="SSID" type="ay" access="read" />
- <property name="BSSID" type="ay" access="read" />
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ <signal name="PropertiesChanged">
+ <arg name="properties" type="a{sv}" />
+ </signal>
+
+ <property name="SSID" type="ay" access="read">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </property>
+ <property name="BSSID" type="ay" access="read">
+ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/>
+ </property>
+ <property name="WPA" type="a{sv}" access="read" />
<property name="Signal" type="n" access="read" />
<property name="Frequency" type="q" access="read" />
</interface>