From c73489c033465f778bbd56513bfcf352a84a1f01 Mon Sep 17 00:00:00 2001 From: "sungho1.shin" <87509909+sungho1shin@users.noreply.github.com> Date: Wed, 25 May 2022 04:21:58 +0900 Subject: [PATCH] Introducing ble adaptation for webOS (#18498) * [webOS] Support ble-wifi commissioning for webOS * Implement the base functions * Add ble central functionality * Clean up sources under src/platform/webos * Wipe out linux legacy left-over * Remove white space * Rename CHIPDevicePlatformEvent as *WebOS* * Change CHIP_DEVICE_LAYER_TARGET from Linux to webos * Change static library name of src/platform/webos as webos * Fix build error of webOS platform * Use resources under src/platform/Linux which is not overriden by webOS * Remove configurationManagerImple.cpp * Change CHIP_CONFIG_MDNS_CACHE_SIZE as 0 * Use webos for chip_device_platform * Fix build error while building webos platform * Build NetworkCommissioning resources * Change header name from Linux to webos * Build openthread to compile NetworkCommissioning resources * Enable chip_enable_openthread * Enable chip_enable_openthread as true for webOS build * Update license * This commit updates the license of ConenctivityManagerImpl.cpp * Fix build structure of webos platform * chip_use_ephemeral_port_for_mdns_unicast_query should be set as true for webOS as well. * chip_enable_openthread should be set as true for webOS as well. * chip_config_network_layer_ble is removed from gn gen command on scripts/ examples/webos_example.sh, and will be set as true for default. chip_enable_ble will follow the chip_config_network_layer_ble. * Initialize the result value inside MainLoop::StartLSMainLoop as true. * [webOS] Remove unused codes in PlatfromManagerImpl * Remove unused codes in PlatfromManagerImpl.cpp * Fix build error due to latest upstream * Compile error due to latest upstreat is fixed. * Sync up and import missing resources from src/platform/Linux * Fix build error on webos platform * Remove type * Remove unnecessary files for initial commit * Remove commented line * Remove garbage on Logging.cpp * Remove commented linea (cont) * Remove garbage on Logging.cpp * Fix build error due to fetch latest upstream * sysroot_webos variable is changed to webos_sysroot * Migrate PlatformManagerImpl.cpp from Linux platform * Restyled by clang-format * Restyled by gn * Fix code-lints errors * Fix code-lints errors Co-authored-by: sungmok.shin Co-authored-by: Restyled.io --- build/config/webos/webos_sdk.gni | 2 +- build/config/webos/webos_sysroot.gni | 1 + scripts/examples/webos_example.sh | 2 +- scripts/tools/check_includes_config.py | 1 + src/lib/dnssd/minimal_mdns/BUILD.gn | 3 +- src/platform/BUILD.gn | 6 +- src/platform/device.gni | 3 +- src/platform/webos/BLEManagerImpl.cpp | 1035 ++++++++++- src/platform/webos/BLEManagerImpl.h | 68 +- src/platform/webos/BUILD.gn | 66 +- src/platform/webos/BlePlatformConfig.h | 1 + src/platform/webos/CHIPDevicePlatformConfig.h | 2 +- src/platform/webos/CHIPDevicePlatformEvent.h | 58 +- src/platform/webos/CHIPPlatformConfig.h | 37 +- src/platform/webos/CHIPWebOSStorage.cpp | 352 ++++ src/platform/webos/CHIPWebOSStorage.h | 105 ++ src/platform/webos/CHIPWebOSStorageIni.cpp | 393 +++++ src/platform/webos/CHIPWebOSStorageIni.h | 63 + src/platform/webos/ChipDeviceScanner.cpp | 375 ++++ src/platform/webos/ChipDeviceScanner.h | 103 ++ .../webos/ConfigurationManagerImpl.cpp | 404 +++++ src/platform/webos/ConfigurationManagerImpl.h | 94 + .../webos/ConnectivityManagerImpl.cpp | 1524 ++++++++++++++++- src/platform/webos/ConnectivityManagerImpl.h | 24 +- src/platform/webos/ConnectivityUtils.cpp | 646 +++++++ src/platform/webos/ConnectivityUtils.h | 66 + src/platform/webos/DeviceInfoProviderImpl.cpp | 364 ++++ src/platform/webos/DeviceInfoProviderImpl.h | 107 ++ .../DeviceNetworkProvisioningDelegateImpl.cpp | 19 +- .../DeviceNetworkProvisioningDelegateImpl.h | 2 +- .../webos/DiagnosticDataProviderImpl.cpp | 802 +++++++++ .../webos/DiagnosticDataProviderImpl.h | 107 ++ src/platform/webos/DnssdImpl.cpp | 867 ++++++++++ src/platform/webos/DnssdImpl.h | 173 ++ src/platform/webos/GlibTypeDeleter.h | 71 + .../webos/KeyValueStoreManagerImpl.cpp | 114 ++ src/platform/webos/KeyValueStoreManagerImpl.h | 79 + src/platform/webos/Logging.cpp | 25 +- src/platform/webos/MainLoop.cpp | 249 +++ src/platform/webos/MainLoop.h | 73 + .../webos/NetworkCommissioningDriver.h | 161 ++ .../NetworkCommissioningThreadDriver.cpp | 205 +++ .../webos/NetworkCommissioningWiFiDriver.cpp | 227 +++ src/platform/webos/PlatformManagerImpl.cpp | 417 +++++ src/platform/webos/PlatformManagerImpl.h | 116 ++ src/platform/webos/PosixConfig.cpp | 591 +++++++ src/platform/webos/PosixConfig.h | 138 ++ src/platform/webos/SystemTimeSupport.cpp | 70 + src/platform/webos/ThreadStackManagerImpl.cpp | 758 ++++++++ src/platform/webos/ThreadStackManagerImpl.h | 167 ++ src/platform/webos/WirelessDefs.h | 186 ++ src/platform/webos/dbus/openthread/BUILD.gn | 25 + .../webos/dbus/openthread/introspect.xml | 476 +++++ 53 files changed, 11844 insertions(+), 179 deletions(-) create mode 100644 src/platform/webos/CHIPWebOSStorage.cpp create mode 100644 src/platform/webos/CHIPWebOSStorage.h create mode 100644 src/platform/webos/CHIPWebOSStorageIni.cpp create mode 100644 src/platform/webos/CHIPWebOSStorageIni.h create mode 100644 src/platform/webos/ChipDeviceScanner.cpp create mode 100644 src/platform/webos/ChipDeviceScanner.h create mode 100644 src/platform/webos/ConfigurationManagerImpl.cpp create mode 100644 src/platform/webos/ConfigurationManagerImpl.h create mode 100644 src/platform/webos/ConnectivityUtils.cpp create mode 100644 src/platform/webos/ConnectivityUtils.h create mode 100644 src/platform/webos/DeviceInfoProviderImpl.cpp create mode 100644 src/platform/webos/DeviceInfoProviderImpl.h create mode 100644 src/platform/webos/DiagnosticDataProviderImpl.cpp create mode 100644 src/platform/webos/DiagnosticDataProviderImpl.h create mode 100644 src/platform/webos/DnssdImpl.cpp create mode 100644 src/platform/webos/DnssdImpl.h create mode 100644 src/platform/webos/GlibTypeDeleter.h create mode 100644 src/platform/webos/KeyValueStoreManagerImpl.cpp create mode 100644 src/platform/webos/KeyValueStoreManagerImpl.h create mode 100644 src/platform/webos/MainLoop.cpp create mode 100644 src/platform/webos/MainLoop.h create mode 100644 src/platform/webos/NetworkCommissioningDriver.h create mode 100644 src/platform/webos/NetworkCommissioningThreadDriver.cpp create mode 100644 src/platform/webos/NetworkCommissioningWiFiDriver.cpp create mode 100644 src/platform/webos/PlatformManagerImpl.cpp create mode 100644 src/platform/webos/PlatformManagerImpl.h create mode 100644 src/platform/webos/PosixConfig.cpp create mode 100644 src/platform/webos/PosixConfig.h create mode 100644 src/platform/webos/SystemTimeSupport.cpp create mode 100644 src/platform/webos/ThreadStackManagerImpl.cpp create mode 100644 src/platform/webos/ThreadStackManagerImpl.h create mode 100644 src/platform/webos/WirelessDefs.h create mode 100644 src/platform/webos/dbus/openthread/BUILD.gn create mode 100644 src/platform/webos/dbus/openthread/introspect.xml diff --git a/build/config/webos/webos_sdk.gni b/build/config/webos/webos_sdk.gni index 21c25c8c18d4e8..313e366f6c0896 100644 --- a/build/config/webos/webos_sdk.gni +++ b/build/config/webos/webos_sdk.gni @@ -13,5 +13,5 @@ # limitations under the License. declare_args() { - sysroot_webos = "" + webos_sysroot = "" } diff --git a/build/config/webos/webos_sysroot.gni b/build/config/webos/webos_sysroot.gni index 41f0feea97fb91..9ed52d81a9531b 100644 --- a/build/config/webos/webos_sysroot.gni +++ b/build/config/webos/webos_sysroot.gni @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import("//build_overrides/build.gni") import("${build_root}/config/webos/webos_sdk.gni") sysroot = webos_sysroot diff --git a/scripts/examples/webos_example.sh b/scripts/examples/webos_example.sh index cb2eddc5332aa6..0621dfd6b52d65 100755 --- a/scripts/examples/webos_example.sh +++ b/scripts/examples/webos_example.sh @@ -43,7 +43,7 @@ echo "" # Build webos example echo "##### Build webos example #####" echo "##### Performing gn gen #####" -gn gen out/host --args="is_debug=false target_os=\"webos\" target_cpu=\"arm\" chip_enable_python_modules=false ar_webos=\"$AR\" cc_webos=\"$CC -Wno-format-security\" cxx_webos=\"$CXX\" sysroot_webos=\"$PKG_CONFIG_SYSROOT_DIR\" chip_build_tests=false enable_syslog=true chip_config_network_layer_ble=false treat_warnings_as_errors=false" +gn gen out/host --args="is_debug=false target_os=\"webos\" target_cpu=\"arm\" chip_enable_python_modules=false ar_webos=\"$AR\" cc_webos=\"$CC -Wno-format-security\" cxx_webos=\"$CXX\" webos_sysroot=\"$PKG_CONFIG_SYSROOT_DIR\" chip_build_tests=false enable_syslog=true treat_warnings_as_errors=false" echo "##### Building by ninja #####" ninja -C out/host diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index ccc3de521f2d16..4d0c640be1a244 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -49,6 +49,7 @@ '/platform/Tizen/', '/platform/P6/', '/platform/bouffalolab/BL602', + '/platform/webos/', r'POSIX\.h$', } diff --git a/src/lib/dnssd/minimal_mdns/BUILD.gn b/src/lib/dnssd/minimal_mdns/BUILD.gn index 0b4d68f3724e54..50e2669ff8da4b 100644 --- a/src/lib/dnssd/minimal_mdns/BUILD.gn +++ b/src/lib/dnssd/minimal_mdns/BUILD.gn @@ -26,7 +26,8 @@ declare_args() { # payloads) and clients need to allocate more resources for this (need one # more UDP socket and corresponding code for the unicast query sending) chip_use_ephemeral_port_for_mdns_unicast_query = - current_os == "mac" || current_os == "linux" || current_os == "android" + current_os == "mac" || current_os == "linux" || current_os == "android" || + current_os == "webos" # Be very verbose regarding packet communication. Will spend extra RAM and # Time to report mdns traffic in terms of sent and seen data. diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index c30f5a93ad8e54..b4d717a0040b60 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -116,7 +116,7 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { if (chip_device_platform == "linux" || chip_device_platform == "darwin" || chip_device_platform == "tizen" || chip_device_platform == "android" || - chip_device_platform == "k32w0") { + chip_device_platform == "k32w0" || chip_device_platform == "webos") { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE=${chip_enable_ble}" ] } @@ -249,7 +249,7 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { } else if (chip_device_platform == "webos") { defines += [ "CHIP_DEVICE_LAYER_TARGET_WEBOS=1", - "CHIP_DEVICE_LAYER_TARGET=Linux", + "CHIP_DEVICE_LAYER_TARGET=webos", "CHIP_DEVICE_CONFIG_ENABLE_WIFI=${chip_enable_wifi}", ] defines -= @@ -351,7 +351,7 @@ if (chip_device_platform != "none") { # Linux has its own NetworkCommissioningThreadDriver if (chip_enable_openthread && chip_device_platform != "linux" && - chip_device_platform != "tizen") { + chip_device_platform != "tizen" && chip_device_platform != "webos") { sources += [ "OpenThread/GenericNetworkCommissioningThreadDriver.cpp", "OpenThread/GenericNetworkCommissioningThreadDriver.h", diff --git a/src/platform/device.gni b/src/platform/device.gni index 94628363fa4705..2258f01cd22010 100755 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -48,7 +48,8 @@ declare_args() { chip_enable_openthread = chip_device_platform == "linux" || chip_device_platform == "qpg" || chip_device_platform == "cc13x2_26x2" || - chip_device_platform == "k32w0" || chip_device_platform == "tizen" + chip_device_platform == "k32w0" || chip_device_platform == "tizen" || + chip_device_platform == "webos" } declare_args() { diff --git a/src/platform/webos/BLEManagerImpl.cpp b/src/platform/webos/BLEManagerImpl.cpp index 0140e06ba60c5d..ef96f2f731d6b6 100644 --- a/src/platform/webos/BLEManagerImpl.cpp +++ b/src/platform/webos/BLEManagerImpl.cpp @@ -27,12 +27,20 @@ #include #include #include +#include #include #include +#include #include #include +#include "MainLoop.h" +#include +#include + +using namespace pbnjson; + #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE using namespace ::nl; @@ -42,11 +50,81 @@ namespace chip { namespace DeviceLayer { namespace Internal { +namespace { + +static constexpr unsigned kNewConnectionScanTimeoutMs = 10000; +static constexpr System::Clock::Timeout kConnectTimeout = System::Clock::Seconds16(10); + +const ChipBleUUID ChipUUID_CHIPoBLEChar_RX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x11 } }; +const ChipBleUUID ChipUUID_CHIPoBLEChar_TX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0x3D, 0x45, 0x59, 0x95, 0x9F, 0x4F, 0x9C, 0x42, 0x9F, + 0x9D, 0x12 } }; +#define CHIP_BLE_GATT_SERVICE "0000fff6-0000-1000-8000-00805f9b34fb" +#define CHIP_BLE_GATT_CHAR_WRITE "18ee2ef5-263d-4559-959f-4f9c429f9d11" +#define CHIP_BLE_GATT_CHAR_READ "18ee2ef5-263d-4559-959f-4f9c429f9d12" + +} // namespace + BLEManagerImpl BLEManagerImpl::sInstance; +void HandleIncomingBleConnection(BLEEndPoint * bleEP) +{ + ChipLogProgress(DeviceLayer, "con rcvd"); +} + +void BLEManagerImpl::InitConnectionData(void) +{ + /* Initialize Hashmap */ + if (!mConnectionMap) + { + mConnectionMap = g_hash_table_new(g_str_hash, g_str_equal); + ChipLogProgress(DeviceLayer, "GATT Connection HashMap created"); + } +} + +gboolean BLEManagerImpl::_BleInitialize(void * userData) +{ + if (sInstance.mFlags.Has(Flags::kWebOSBLELayerInitialized)) + { + ChipLogProgress(DeviceLayer, "BLE Already Initialized"); + return true; + } + + sInstance.InitConnectionData(); + + // Should add BT callback + + sInstance.mFlags.Set(Flags::kWebOSBLELayerInitialized); + ChipLogProgress(DeviceLayer, "BLE Initialized"); + sInstance.mMainContext = g_main_context_get_thread_default(); + return true; +} + CHIP_ERROR BLEManagerImpl::_Init() { CHIP_ERROR err; + bool ret; + + err = BleLayer::Init(this, this, this, &DeviceLayer::SystemLayer()); + SuccessOrExit(err); + + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Enabled; + mFlags.ClearAll().Set(Flags::kAdvertisingEnabled, CHIP_DEVICE_CONFIG_CHIPOBLE_ENABLE_ADVERTISING_AUTOSTART && !mIsCentral); + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + + memset(mDeviceName, 0, sizeof(mDeviceName)); + + OnChipBleConnectReceived = HandleIncomingBleConnection; + + ret = MainLoop::Instance().Init(_BleInitialize); + VerifyOrExit(ret != false, err = CHIP_ERROR_INTERNAL); + + ret = MainLoop::Instance().StartLSMainLoop(); + VerifyOrExit(ret != false, err = CHIP_ERROR_INTERNAL); + + PlatformMgr().ScheduleWork(DriveBLEState, 0); + +exit: return err; } @@ -60,32 +138,101 @@ CHIP_ERROR BLEManagerImpl::_Shutdown() CHIP_ERROR BLEManagerImpl::_SetCHIPoBLEServiceMode(CHIPoBLEServiceMode val) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + if (val != mServiceMode) + { + mServiceMode = val; + PlatformMgr().ScheduleWork(DriveBLEState, 0); + } + +exit: + return err; } CHIP_ERROR BLEManagerImpl::_SetAdvertisingEnabled(bool val) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + if (mFlags.Has(Flags::kAdvertisingEnabled) != val) + { + mFlags.Set(Flags::kAdvertisingEnabled, val); + } + + PlatformMgr().ScheduleWork(DriveBLEState, 0); + + return err; } CHIP_ERROR BLEManagerImpl::_SetAdvertisingMode(BLEAdvertisingMode mode) { - return CHIP_ERROR_NOT_IMPLEMENTED; + switch (mode) + { + case BLEAdvertisingMode::kFastAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, true); + break; + case BLEAdvertisingMode::kSlowAdvertising: + mFlags.Set(Flags::kFastAdvertisingEnabled, false); + break; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + mFlags.Set(Flags::kAdvertisingRefreshNeeded); + PlatformMgr().ScheduleWork(DriveBLEState, 0); + return CHIP_NO_ERROR; } CHIP_ERROR BLEManagerImpl::_GetDeviceName(char * buf, size_t bufSize) { - return CHIP_ERROR_NOT_IMPLEMENTED; + if (strlen(mDeviceName) >= bufSize) + { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + strcpy(buf, mDeviceName); + + return CHIP_NO_ERROR; } CHIP_ERROR BLEManagerImpl::_SetDeviceName(const char * deviceName) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(mServiceMode != ConnectivityManager::kCHIPoBLEServiceMode_NotSupported, err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + if (deviceName != nullptr && deviceName[0] != 0) + { + VerifyOrExit(strlen(deviceName) < kMaxDeviceNameLength, err = CHIP_ERROR_INVALID_ARGUMENT); + strcpy(mDeviceName, deviceName); + mFlags.Set(Flags::kUseCustomDeviceName); + } + else + { + uint16_t discriminator; + SuccessOrExit(err = GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator)); + snprintf(mDeviceName, sizeof(mDeviceName), "%s%04u", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, discriminator); + mDeviceName[kMaxDeviceNameLength] = 0; + mFlags.Clear(Flags::kUseCustomDeviceName); + } + +exit: + return err; +} + +uint16_t BLEManagerImpl::_NumConnections() +{ + uint16_t numCons = 0; + return numCons; } CHIP_ERROR BLEManagerImpl::ConfigureBle(uint32_t aAdapterId, bool aIsCentral) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + mIsCentral = aIsCentral; + + return err; } CHIP_ERROR BLEManagerImpl::StartBLEAdvertising() @@ -98,37 +245,426 @@ CHIP_ERROR BLEManagerImpl::StopBLEAdvertising() return CHIP_ERROR_NOT_IMPLEMENTED; } -void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) {} +void BLEManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + switch (event->Type) + { + case DeviceEventType::kCHIPoBLESubscribe: + HandleSubscribeReceived(event->CHIPoBLESubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + { + ChipDeviceEvent connectionEvent; + connectionEvent.Type = DeviceEventType::kCHIPoBLEConnectionEstablished; + PlatformMgr().PostEventOrDie(&connectionEvent); + } + break; -void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent) {} + case DeviceEventType::kCHIPoBLEUnsubscribe: + HandleUnsubscribeReceived(event->CHIPoBLEUnsubscribe.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; -uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const {} + case DeviceEventType::kCHIPoBLEWriteReceived: + HandleWriteReceived(event->CHIPoBLEWriteReceived.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX, + PacketBufferHandle::Adopt(event->CHIPoBLEWriteReceived.Data)); + break; -bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) + case DeviceEventType::kCHIPoBLEIndicateConfirm: + HandleIndicationConfirmation(event->CHIPoBLEIndicateConfirm.ConId, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX); + break; + + case DeviceEventType::kCHIPoBLEConnectionError: + HandleConnectionError(event->CHIPoBLEConnectionError.ConId, event->CHIPoBLEConnectionError.Reason); + break; + case DeviceEventType::kFabricMembershipChange: + case DeviceEventType::kServiceProvisioningChange: + case DeviceEventType::kAccountPairingChange: + + // If CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED is enabled, and there is a change to the + // device's provisioning state, then automatically disable CHIPoBLE advertising if the device + // is now fully provisioned. +#if CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + if (ConfigurationMgr().IsFullyProvisioned()) + { + mFlags.Clear(Flags::kAdvertisingEnabled); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising disabled because device is fully provisioned"); + } +#endif // CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + + // Force the advertising configuration to be refreshed to reflect new provisioning state. + mFlags.Clear(Flags::kAdvertisingConfigured); + + DriveBLEState(); + break; + default: + HandlePlatformSpecificBLEEvent(event); + break; + } +} + +void BLEManagerImpl::HandlePlatformSpecificBLEEvent(const ChipDeviceEvent * apEvent) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + bool controlOpComplete = false; + ChipLogDetail(DeviceLayer, "HandlePlatformSpecificBLEEvent %d", apEvent->Type); + switch (apEvent->Type) + { + case DeviceEventType::kPlatformWebOSBLECentralConnected: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionComplete(mBLEScanConfig.mAppState, + apEvent->Platform.BLECentralConnected.mConnection); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformWebOSBLECentralConnectFailed: + if (mBLEScanConfig.mBleScanState == BleScanState::kConnecting) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, apEvent->Platform.BLECentralConnectFailed.mError); + CleanScanConfig(); + } + break; + case DeviceEventType::kPlatformWebOSBLEWriteComplete: + HandleWriteConfirmation(apEvent->Platform.BLEWriteComplete.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_RX); + break; + case DeviceEventType::kPlatformWebOSBLESubscribeOpComplete: + if (apEvent->Platform.BLESubscribeOpComplete.mIsSubscribed) + HandleSubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + else + HandleUnsubscribeComplete(apEvent->Platform.BLESubscribeOpComplete.mConnection, &CHIP_BLE_SVC_ID, + &ChipUUID_CHIPoBLEChar_TX); + break; + case DeviceEventType::kPlatformWebOSBLEIndicationReceived: + HandleIndicationReceived(apEvent->Platform.BLEIndicationReceived.mConnection, &CHIP_BLE_SVC_ID, &ChipUUID_CHIPoBLEChar_TX, + PacketBufferHandle::Adopt(apEvent->Platform.BLEIndicationReceived.mData)); + break; + case DeviceEventType::kPlatformWebOSBLEPeripheralAdvConfiguredComplete: + VerifyOrExit(apEvent->Platform.BLEPeripheralAdvConfiguredComplete.mIsSuccess, err = CHIP_ERROR_INCORRECT_STATE); + sInstance.mFlags.Set(Flags::kAdvertisingConfigured).Clear(Flags::kControlOpInProgress); + controlOpComplete = true; + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising config complete"); + break; + case DeviceEventType::kPlatformWebOSBLEPeripheralAdvStartComplete: + VerifyOrExit(apEvent->Platform.BLEPeripheralAdvStartComplete.mIsSuccess, err = CHIP_ERROR_INCORRECT_STATE); + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + + if (!sInstance.mFlags.Has(Flags::kAdvertising)) + { + sInstance.mFlags.Set(Flags::kAdvertising); + } + + break; + case DeviceEventType::kPlatformWebOSBLEPeripheralAdvStopComplete: + VerifyOrExit(apEvent->Platform.BLEPeripheralAdvStopComplete.mIsSuccess, err = CHIP_ERROR_INCORRECT_STATE); + + sInstance.mFlags.Clear(Flags::kControlOpInProgress).Clear(Flags::kAdvertisingRefreshNeeded); + + // Transition to the not Advertising state... + if (sInstance.mFlags.Has(Flags::kAdvertising)) + { + sInstance.mFlags.Clear(Flags::kAdvertising); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising stopped"); + } + break; + case DeviceEventType::kPlatformWebOSBLEPeripheralRegisterAppComplete: + VerifyOrExit(apEvent->Platform.BLEPeripheralRegisterAppComplete.mIsSuccess, err = CHIP_ERROR_INCORRECT_STATE); + mFlags.Set(Flags::kAppRegistered); + controlOpComplete = true; + break; + default: + break; + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + sInstance.mFlags.Clear(Flags::kControlOpInProgress); + } + + if (controlOpComplete) + { + mFlags.Clear(Flags::kControlOpInProgress); + DriveBLEState(); + } +} + +uint16_t BLEManagerImpl::GetMTU(BLE_CONNECTION_OBJECT conId) const +{ + return 20; +} + +bool BLEManagerImpl::gattMonitorCharateristicsCb(LSHandle * sh, LSMessage * message, void * userData) +{ + BLEConnection * conn = nullptr; + conn = (BLEConnection *) userData; + + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return true; + + const char * payload = jvalue_tostring(parsedObj, input_schema); + + pbnjson::JValue jvalue = pbnjson::JDomParser::fromString(std::string(payload)); + + if (jvalue.hasKey("returnValue")) + { + ChipLogProgress(DeviceLayer, "gattMonitorCharateristicsCb payload returnValue is %d", jvalue["returnValue"].asBool()); + + if (jvalue["changed"]["value"].hasKey("bytes") == true) + { + ChipLogProgress(DeviceLayer, "received read value is %s", jvalue["changed"]["value"].stringify().c_str()); + + pbnjson::JValue value = jvalue["changed"]["value"]; + + uint8_t * values = (uint8_t *) malloc(sizeof(uint8_t) * value["bytes"].arraySize()); + + for (int i = 0; i < value["bytes"].arraySize(); i++) + { + values[i] = value["bytes"][i].asNumber(); + } + + sInstance.HandleTXCharChanged(conn, values, value["bytes"].arraySize()); + } + } + + return true; +} + +bool BLEManagerImpl::gattWriteDescriptorValueCb(LSHandle * sh, LSMessage * message, void * userData) { + BLEConnection * conn = nullptr; + conn = (BLEConnection *) userData; + + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return false; + + const char * payload = jvalue_tostring(parsedObj, input_schema); + + ChipLogProgress(DeviceLayer, "gattWriteDescriptorValueCb payload is %s", payload); + sInstance.HandleSubscribeOpComplete(conn, true); + + return true; +} + +bool BLEManagerImpl::SubscribeCharacteristicToWebOS(void * bleConnObj, const uint8_t * svcId, const uint8_t * charId) +{ + pbnjson::JValue valueForMonitor = pbnjson::JObject(); + + valueForMonitor.put("clientId", std::string(mClientId)); + valueForMonitor.put("service", std::string(CHIP_BLE_GATT_SERVICE)); + pbnjson::JValue bytesJArray = pbnjson::JArray(); + bytesJArray.append(std::string(CHIP_BLE_GATT_CHAR_READ)); + + valueForMonitor.put("characteristics", bytesJArray); + valueForMonitor.put("subscribe", true); + + int ret = 0; + + if (mLSHandle == nullptr) + { + ChipLogError(DeviceLayer, "LS handle is null"); + return false; + } + + ret = LSCall(mLSHandle, "luna://com.webos.service.bluetooth2/gatt/monitorCharacteristics", valueForMonitor.stringify().c_str(), + gattMonitorCharateristicsCb, bleConnObj, NULL, NULL); + + sleep(2); + + pbnjson::JValue valueForDescriptor = pbnjson::JObject(); + valueForDescriptor.put("clientId", std::string(mClientId)); + valueForDescriptor.put("service", std::string(CHIP_BLE_GATT_SERVICE)); + valueForDescriptor.put("characteristic", std::string(CHIP_BLE_GATT_CHAR_READ)); + + valueForDescriptor.put("descriptor", std::string("00002902-0000-1000-8000-00805f9b34fb")); + + bool subscribe = true; // current is true. + + pbnjson::JValue valueParam = pbnjson::JObject(); + pbnjson::JValue bytesForDescriptorJArray = pbnjson::JArray(); // "bytes": [ ] + if (subscribe) + { + bytesForDescriptorJArray.append(2); + bytesForDescriptorJArray.append(0); + } + else + { + bytesForDescriptorJArray.append(0); + bytesForDescriptorJArray.append(0); + } + + valueParam.put("bytes", bytesForDescriptorJArray); + valueForDescriptor.put("value", valueParam); + + ChipLogProgress(Ble, "SubscribeCharacteristicToWebOS Param : valueForDescriptor %s", valueForDescriptor.stringify().c_str()); + + ret = LSCall(mLSHandle, "luna://com.webos.service.bluetooth2/gatt/writeDescriptorValue", valueForDescriptor.stringify().c_str(), + gattWriteDescriptorValueCb, bleConnObj, NULL, NULL); + + if (ret != 1) + return false; + return true; } +bool BLEManagerImpl::SubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) +{ + bool result = false; + result = SubscribeCharacteristicToWebOS(conId, static_cast(svcId->bytes), + static_cast(charId->bytes)); + return result; +} + bool BLEManagerImpl::UnsubscribeCharacteristic(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const ChipBleUUID * charId) { + ChipLogError(Ble, "UnsubscribeCharacteristic: Not implemented"); return true; } bool BLEManagerImpl::CloseConnection(BLE_CONNECTION_OBJECT conId) { + ChipLogError(Ble, "CloseConnection: Not implemented"); return true; } bool BLEManagerImpl::SendIndication(BLE_CONNECTION_OBJECT conId, const ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, chip::System::PacketBufferHandle pBuf) { + ChipLogError(Ble, "SendIndication: Not implemented"); + return true; +} + +bool BLEManagerImpl::gattWriteValueCb(LSHandle * sh, LSMessage * message, void * userData) +{ + BLEConnection * conn = nullptr; + conn = (BLEConnection *) userData; + + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return true; + + const char * payload = jvalue_tostring(parsedObj, input_schema); + + ChipLogProgress(DeviceLayer, "gattWriteValueCb payload is %s", payload); + sInstance.HandleWriteComplete(conn); + + return true; +} + +bool BLEManagerImpl::SendWriteRequestToWebOS(void * bleConnObj, const uint8_t * svcId, const uint8_t * charId, const uint8_t * pBuf, + uint32_t pBufDataLen) +{ + BLEConnection * conn = (BLEConnection *) bleConnObj; + std::ostringstream cvt; + + for (int i = 0; i < (int) pBufDataLen; i++) + { + cvt << std::hex << std::setfill('0') << std::setw(2) << (int) pBuf[i]; + } + + pbnjson::JValue values = pbnjson::JObject(); + values.put("clientId", std::string(mClientId)); + values.put("data", cvt.str().c_str()); + + ChipLogProgress(Ble, "SendWriteRequestToWebOS Param : value %s", values.stringify().c_str()); + + std::string clientId = values["clientId"].asString(); + std::string valueType = "bytes"; + std::string value = values["data"].asString(); + + pbnjson::JValue param = pbnjson::JObject(); + pbnjson::JValue valueParam = pbnjson::JObject(); + param.put("clientId", clientId); + param.put("service", std::string(CHIP_BLE_GATT_SERVICE)); + param.put("characteristic", std::string(CHIP_BLE_GATT_CHAR_WRITE)); + + if (valueType == "byte") + { + valueParam.put("bytes", pbnjson::JArray{ std::stoi(value) }); + } + else if (valueType == "bytes") + { + pbnjson::JValue bytesJArray = JArray(); // "bytes": [ ] + std::vector bytes; + for (int i = 0; i < (int) value.length(); i += 2) + { + std::string byteString = value.substr(i, 2); + char c = (char) strtol(byteString.c_str(), NULL, 16); + bytesJArray.append(c); + } + valueParam.put("bytes", bytesJArray); + } + else if (valueType == "string") + { + valueParam.put("string", value); + } + else + { + } + param.put("value", valueParam); + + ChipLogProgress(Ble, "SendWriteRequestToWebOS Param : param %s", param.stringify().c_str()); + + int ret = 0; + + if (mLSHandle == nullptr) + { + ChipLogError(DeviceLayer, "LS handle is null"); + return false; + } + + ret = LSCall(mLSHandle, "luna://com.webos.service.bluetooth2/gatt/writeCharacteristicValue", param.stringify().c_str(), + gattWriteValueCb, conn, NULL, NULL); + + VerifyOrExit(ret == 1, ChipLogError(DeviceLayer, "Failed to write characteristic . ret [%d]", ret)); + +exit: + if (ret != 1) + return false; + return true; } bool BLEManagerImpl::SendWriteRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, chip::System::PacketBufferHandle pBuf) { - return true; + bool result = false; + result = SendWriteRequestToWebOS(conId, static_cast(svcId->bytes), static_cast(charId->bytes), + pBuf->Start(), pBuf->DataLength()); + + return result; } bool BLEManagerImpl::SendReadRequest(BLE_CONNECTION_OBJECT conId, const Ble::ChipBleUUID * svcId, const Ble::ChipBleUUID * charId, @@ -145,25 +681,179 @@ bool BLEManagerImpl::SendReadResponse(BLE_CONNECTION_OBJECT conId, BLE_READ_REQU return true; } -void BLEManagerImpl::HandleNewConnection(BLE_CONNECTION_OBJECT conId) {} +void BLEManagerImpl::AddConnectionData(const char * remoteAddr) +{ + BLEConnection * conn; + ChipLogProgress(DeviceLayer, "AddConnectionData for [%s]", remoteAddr); + + if (!g_hash_table_lookup(mConnectionMap, remoteAddr)) + { + ChipLogProgress(DeviceLayer, "Not Found in Map"); + conn = (BLEConnection *) g_malloc0(sizeof(BLEConnection)); + conn->peerAddr = g_strdup(remoteAddr); + + if (sInstance.mIsCentral) + { + g_hash_table_insert(mConnectionMap, (gpointer) conn->peerAddr, conn); + ChipLogProgress(DeviceLayer, "New Connection Added for [%s]", remoteAddr); + HandleNewConnection(conn); + } + else + { + /* Local Device is BLE Peripheral Role, assume remote is CHIP Central */ + conn->isChipDevice = true; + g_hash_table_insert(mConnectionMap, (gpointer) conn->peerAddr, conn); + ChipLogProgress(DeviceLayer, "New Connection Added for [%s]", remoteAddr); + } + } +} + +void BLEManagerImpl::HandleConnectionEvent(bool connected, const char * remoteAddress) +{ + if (connected) + { + ChipLogProgress(DeviceLayer, "Device Connected [%s]", remoteAddress); + AddConnectionData(remoteAddress); + } + else + { + ChipLogProgress(DeviceLayer, "Device DisConnected [%s]", remoteAddress); + } +} + +void BLEManagerImpl::HandleNewConnection(BLE_CONNECTION_OBJECT conId) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLECentralConnected; + event.Platform.BLECentralConnected.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error) +{ + if (sInstance.mIsCentral) + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLECentralConnectFailed; + event.Platform.BLECentralConnectFailed.mError = error; + PlatformMgr().PostEventOrDie(&event); + } +} + +void BLEManagerImpl::HandleWriteComplete(BLE_CONNECTION_OBJECT conId) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEWriteComplete; + event.Platform.BLEWriteComplete.mConnection = conId; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLESubscribeOpComplete; + event.Platform.BLESubscribeOpComplete.mConnection = conId; + event.Platform.BLESubscribeOpComplete.mIsSubscribed = subscribed; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf = System::PacketBufferHandle::NewWithData(value, len); -void BLEManagerImpl::HandleConnectFailed(CHIP_ERROR error) {} + ChipLogDetail(DeviceLayer, "Indication received, conn = %p", conId); -void BLEManagerImpl::HandleWriteComplete(BLE_CONNECTION_OBJECT conId) {} + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); -void BLEManagerImpl::HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed) {} + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEIndicationReceived; + event.Platform.BLEIndicationReceived.mConnection = conId; + event.Platform.BLEIndicationReceived.mData = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); -void BLEManagerImpl::HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) {} +exit: + if (err != CHIP_NO_ERROR) + ChipLogError(DeviceLayer, "HandleTXCharChanged() failed: %s", ErrorStr(err)); +} -void BLEManagerImpl::HandleRXCharWrite(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) {} +void BLEManagerImpl::HandleRXCharWrite(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::PacketBufferHandle buf; -void BLEManagerImpl::CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT conId) {} + // Copy the data to a packet buffer. + buf = System::PacketBufferHandle::NewWithData(value, len); + VerifyOrExit(!buf.IsNull(), err = CHIP_ERROR_NO_MEMORY); + + // Post an event to the Chip queue to deliver the data into the Chip stack. + { + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEWriteReceived; + ChipLogProgress(Ble, "Write request received debug %p", conId); + event.CHIPoBLEWriteReceived.ConId = conId; + event.CHIPoBLEWriteReceived.Data = std::move(buf).UnsafeRelease(); + PlatformMgr().PostEventOrDie(&event); + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "HandleRXCharWrite() failed: %s", ErrorStr(err)); + } +} void BLEManagerImpl::HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT conId) {} -void BLEManagerImpl::HandleTXComplete(BLE_CONNECTION_OBJECT conId) {} +void BLEManagerImpl::HandleTXComplete(BLE_CONNECTION_OBJECT conId) +{ + // Post an event to the Chip queue to process the indicate confirmation. + ChipDeviceEvent event; + event.Type = DeviceEventType::kCHIPoBLEIndicateConfirm; + event.CHIPoBLEIndicateConfirm.ConId = conId; + PlatformMgr().PostEventOrDie(&event); +} -void BLEManagerImpl::DriveBLEState() {} +void BLEManagerImpl::DriveBLEState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + // Perform any initialization actions that must occur after the Chip task is running. + if (!mFlags.Has(Flags::kAsyncInitCompleted)) + { + mFlags.Set(Flags::kAsyncInitCompleted); + + // If CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED is enabled, + // disable CHIPoBLE advertising if the device is fully provisioned. +#if CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + if (ConfigurationMgr().IsFullyProvisioned()) + { + mFlags.Clear(Flags::kAdvertisingEnabled); + ChipLogProgress(DeviceLayer, "CHIPoBLE advertising disabled because device is fully provisioned"); + } +#endif // CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + ExitNow(); + } + + // If there's already a control operation in progress, wait until it completes. + VerifyOrExit(!mFlags.Has(Flags::kControlOpInProgress), /* */); + + // Initializes the Bluez BLE layer if needed. + if (mServiceMode == ConnectivityManager::kCHIPoBLEServiceMode_Enabled && !mFlags.Has(Flags::kWebOSBLELayerInitialized)) + { + mFlags.Set(Flags::kWebOSBLELayerInitialized); + } + +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Disabling CHIPoBLE service due to error: %s", ErrorStr(err)); + mServiceMode = ConnectivityManager::kCHIPoBLEServiceMode_Disabled; + } +} void BLEManagerImpl::DriveBLEState(intptr_t arg) { @@ -175,30 +865,315 @@ void BLEManagerImpl::NotifyChipConnectionClosed(BLE_CONNECTION_OBJECT conId) ChipLogProgress(Ble, "Got notification regarding chip connection closure"); } -void BLEManagerImpl::InitiateScan(BleScanState scanType) {} +void BLEManagerImpl::InitiateScan(BleScanState scanType) +{ + DriveBLEState(); + + if (scanType == BleScanState::kNotScanning) + { + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INCORRECT_STATE); + ChipLogError(Ble, "Invalid scan type requested"); + return; + } + + mDeviceScanner = Internal::ChipDeviceScanner::Create(this); + mBLEScanConfig.mBleScanState = scanType; + + if (!mDeviceScanner) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_INTERNAL); + ChipLogError(Ble, "Failed to create a BLE device scanner"); + return; + } + + CHIP_ERROR err = mDeviceScanner->StartChipScan(kNewConnectionScanTimeoutMs); + + if (err != CHIP_NO_ERROR) + { + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; + ChipLogError(Ble, "Failed to start a BLE can: %s", chip::ErrorStr(err)); + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, err); + return; + } + + ChipLogError(DeviceLayer, "BLE Scan Initiation Successful"); +} + +void BLEManagerImpl::CleanScanConfig() +{ + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; +} -void BLEManagerImpl::CleanScanConfig() {} +void BLEManagerImpl::InitiateScan(intptr_t arg) +{ + sInstance.InitiateScan(static_cast(arg)); +} -void BLEManagerImpl::InitiateScan(intptr_t arg) {} +void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const uint16_t connDiscriminator) +{ + mBLEScanConfig.mDiscriminator = connDiscriminator; + mBLEScanConfig.mAppState = appState; -void BLEManagerImpl::NewConnection(BleLayer * bleLayer, void * appState, const uint16_t connDiscriminator) {} + // Scan initiation performed async, to ensure that the BLE subsystem is initialized. + PlatformMgr().ScheduleWork(InitiateScan, static_cast(BleScanState::kScanForDiscriminator)); +} CHIP_ERROR BLEManagerImpl::CancelConnection() { return CHIP_ERROR_NOT_IMPLEMENTED; } -void BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(bool aIsSuccess, void * apAppstate) {} +void BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(bool aIsSuccess, void * apAppstate) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEPeripheralRegisterAppComplete; + event.Platform.BLEPeripheralRegisterAppComplete.mIsSuccess = aIsSuccess; + event.Platform.BLEPeripheralRegisterAppComplete.mpAppstate = apAppstate; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvConfiguredComplete(bool aIsSuccess, void * apAppstate) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEPeripheralAdvConfiguredComplete; + event.Platform.BLEPeripheralAdvConfiguredComplete.mIsSuccess = aIsSuccess; + event.Platform.BLEPeripheralAdvConfiguredComplete.mpAppstate = apAppstate; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(bool aIsSuccess, void * apAppstate) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEPeripheralAdvStartComplete; + event.Platform.BLEPeripheralAdvStartComplete.mIsSuccess = aIsSuccess; + event.Platform.BLEPeripheralAdvStartComplete.mpAppstate = apAppstate; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(bool aIsSuccess, void * apAppstate) +{ + ChipDeviceEvent event; + event.Type = DeviceEventType::kPlatformWebOSBLEPeripheralAdvStopComplete; + event.Platform.BLEPeripheralAdvStopComplete.mIsSuccess = aIsSuccess; + event.Platform.BLEPeripheralAdvStopComplete.mpAppstate = apAppstate; + PlatformMgr().PostEventOrDie(&event); +} + +void BLEManagerImpl::OnChipScanComplete(void) +{ + if (mBLEScanConfig.mBleScanState != BleScanState::kScanForDiscriminator && + mBLEScanConfig.mBleScanState != BleScanState::kScanForAddress) + { + ChipLogProgress(DeviceLayer, "Scan complete notification without an active scan."); + return; + } + + ChipLogError(DeviceLayer, "Scan Completed with Timeout: Notify Upstream."); + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_TIMEOUT); + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; +} + +void BLEManagerImpl::OnScanComplete() +{ + if (mBLEScanConfig.mBleScanState != BleScanState::kScanForDiscriminator && + mBLEScanConfig.mBleScanState != BleScanState::kScanForAddress) + { + ChipLogProgress(Ble, "Scan complete notification without an active scan."); + return; + } + + BleConnectionDelegate::OnConnectionError(mBLEScanConfig.mAppState, CHIP_ERROR_TIMEOUT); + mBLEScanConfig.mBleScanState = BleScanState::kNotScanning; +} + +bool BLEManagerImpl::gattGetServiceCb(LSHandle * sh, LSMessage * message, void * userData) +{ + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return true; + + const char * payload = jvalue_tostring(parsedObj, input_schema); + + ChipLogProgress(DeviceLayer, "gattGetServiceCb payload is %s", payload); + + sInstance.HandleConnectionEvent(true, sInstance.mRemoteAddress); + + return true; +} + +bool BLEManagerImpl::gattConnectCb(LSHandle * sh, LSMessage * message, void * userData) +{ + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return true; + + const char * payload = jvalue_tostring(parsedObj, input_schema); + + ChipLogProgress(DeviceLayer, "gattConnectCb payload is %s", payload); + + jvalue_ref clientIdObj = { 0 }; + if (jobject_get_exists(parsedObj, J_CSTR_TO_BUF("clientId"), &clientIdObj)) + { + raw_buffer clientId_buf = jstring_get(clientIdObj); + char * clientId = g_strdup(clientId_buf.m_str); + jstring_free_buffer(clientId_buf); + + printf("clientId : %s \n", clientId); + + if (sInstance.mClientId != nullptr) + { + g_free(sInstance.mClientId); + } + sInstance.mClientId = g_strdup(clientId); + + g_free(clientId); + } + + jvalue_ref addressObj = { 0 }; + if (jobject_get_exists(parsedObj, J_CSTR_TO_BUF("address"), &addressObj)) + { + raw_buffer address_buf = jstring_get(addressObj); + char * address = g_strdup(address_buf.m_str); + jstring_free_buffer(address_buf); + + printf("address : %s \n", address); + + if (sInstance.mRemoteAddress != nullptr) + { + g_free(sInstance.mRemoteAddress); + } + sInstance.mRemoteAddress = g_strdup(address); + + g_free(address); + } + + if (sInstance.mLSHandle == nullptr) + { + ChipLogError(DeviceLayer, "LS handle is null"); + return false; + } + sleep(1); + char ls2Param[100]; + snprintf(ls2Param, 100, "{\"address\":\"%s\"}", sInstance.mRemoteAddress); + + ChipLogProgress(DeviceLayer, "getService: Addr [%s]", sInstance.mRemoteAddress); + + int ret = 0; + + ret = LSCall(sInstance.mLSHandle, "luna://com.webos.service.bluetooth2/gatt/getServices", ls2Param, gattGetServiceCb, NULL, + NULL, NULL); + + VerifyOrExit(ret == 1, ChipLogError(DeviceLayer, "Failed to get GATT service . ret [%d]", ret)); + +exit: + if (ret != 1) + sInstance.HandleConnectFailed(CHIP_ERROR_INTERNAL); + + return true; +} + +gboolean BLEManagerImpl::ConnectChipThing(gpointer userData) +{ + int ret = 0; + + if (sInstance.mLSHandle == nullptr) + { + ChipLogError(DeviceLayer, "LS handle is null"); + return false; + } + + char * address = (char *) userData; + ChipLogProgress(DeviceLayer, "ConnectRequest: Addr [%s]", address); + + char ls2Param[100]; + snprintf(ls2Param, 100, "{\"address\":\"%s\"}", address); + + ret = LSCall(sInstance.mLSHandle, "luna://com.webos.service.bluetooth2/gatt/connect", ls2Param, gattConnectCb, userData, NULL, + NULL); -void BLEManagerImpl::NotifyBLEPeripheralAdvConfiguredComplete(bool aIsSuccess, void * apAppstate) {} + VerifyOrExit(ret == 1, ChipLogError(DeviceLayer, "Failed to create GATT client. ret [%d]", ret)); -void BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(bool aIsSuccess, void * apAppstate) {} + ChipLogProgress(DeviceLayer, "GATT Connect Issued"); +exit: + if (ret != 1) + sInstance.HandleConnectFailed(CHIP_ERROR_INTERNAL); + + g_free(address); + return G_SOURCE_REMOVE; +} + +void BLEManagerImpl::ConnectHandler(const char * address) +{ + ChipLogProgress(DeviceLayer, "Try to connect New device scanned: %s", address); -void BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(bool aIsSuccess, void * apAppstate) {} + mLSHandle = MainLoop::Instance().mLSHandle; -void BLEManagerImpl::OnDeviceScanned(BluezDevice1 * device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) {} + if (mLSHandle == nullptr) + { + ChipLogError(DeviceLayer, "LS handle is null"); + return; + } -void BLEManagerImpl::OnScanComplete() {} + GSource * idleSource; + + idleSource = g_idle_source_new(); + g_source_set_callback(idleSource, ConnectChipThing, g_strdup(address), nullptr); + g_source_set_priority(idleSource, G_PRIORITY_HIGH_IDLE); + g_source_attach(idleSource, sInstance.mMainContext); + g_source_unref(idleSource); +} + +void BLEManagerImpl::OnChipDeviceScanned(char * address) +{ + ChipLogProgress(DeviceLayer, "New device scanned: %s", address); + + if (mBLEScanConfig.mBleScanState == BleScanState::kScanForDiscriminator) + { + ChipLogProgress(DeviceLayer, "Device discriminator match. Attempting to connect."); + } + else if (mBLEScanConfig.mBleScanState == BleScanState::kScanForAddress) + { + if (strcmp(address, "e4:5f:01:20:49:c0") == 0) + { + ChipLogProgress(DeviceLayer, "SSH Ignore : %s", address); + return; + } + ChipLogProgress(DeviceLayer, "Device address match. Attempting to connect."); + } + else + { + ChipLogError(DeviceLayer, "Unknown discovery type. Ignoring scanned device."); + return; + } + + /* Set CHIP Connecting state */ + mBLEScanConfig.mBleScanState = BleScanState::kConnecting; + mDeviceScanner->StopChipScan(); + + /* Initiate Connect */ + ConnectHandler(address); +} } // namespace Internal } // namespace DeviceLayer diff --git a/src/platform/webos/BLEManagerImpl.h b/src/platform/webos/BLEManagerImpl.h index ebb0b53eb74dcc..8b04ba41b69560 100644 --- a/src/platform/webos/BLEManagerImpl.h +++ b/src/platform/webos/BLEManagerImpl.h @@ -28,27 +28,15 @@ #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE +#include "ChipDeviceScanner.h" +#include + namespace chip { namespace DeviceLayer { namespace Internal { void HandleIncomingBleConnection(Ble::BLEEndPoint * bleEP); -struct BLEAdvConfig -{ - char * mpBleName = nullptr; - uint32_t mAdapterId = 0; - uint8_t mMajor = 0; - uint8_t mMinor = 0; - uint16_t mVendorId = 0; - uint16_t mProductId = 0; - uint64_t mDeviceId = 0; - uint8_t mPairingStatus = 0; - ChipAdvType mType; - uint16_t mDuration = 0; - const char * mpAdvertisingUUID = nullptr; -}; - enum class BleScanState : uint8_t { kNotScanning, @@ -89,14 +77,12 @@ class BLEManagerImpl final : public BLEManager, public: CHIP_ERROR ConfigureBle(uint32_t aAdapterId, bool aIsCentral); - // Driven by BlueZ IO static void HandleNewConnection(BLE_CONNECTION_OBJECT conId); static void HandleConnectFailed(CHIP_ERROR error); static void HandleWriteComplete(BLE_CONNECTION_OBJECT conId); static void HandleSubscribeOpComplete(BLE_CONNECTION_OBJECT conId, bool subscribed); static void HandleTXCharChanged(BLE_CONNECTION_OBJECT conId, const uint8_t * value, size_t len); static void HandleRXCharWrite(BLE_CONNECTION_OBJECT user_data, const uint8_t * value, size_t len); - static void CHIPoBluez_ConnectionClosed(BLE_CONNECTION_OBJECT user_data); static void HandleTXCharCCCDWrite(BLE_CONNECTION_OBJECT user_data); static void HandleTXComplete(BLE_CONNECTION_OBJECT user_data); @@ -151,8 +137,9 @@ class BLEManagerImpl final : public BLEManager, CHIP_ERROR CancelConnection() override; // ===== Members that implement virtual methods on ChipDeviceScannerDelegate - void OnDeviceScanned(BluezDevice1 * device, const chip::Ble::ChipBLEDeviceIdentificationInfo & info) override; void OnScanComplete() override; + void OnChipDeviceScanned(char * address) override; + void OnChipScanComplete() override; // ===== Members for internal use by the following friends. @@ -165,9 +152,9 @@ class BLEManagerImpl final : public BLEManager, enum class Flags : uint16_t { kAsyncInitCompleted = 0x0001, /**< One-time asynchronous initialization actions have been performed. */ - kBluezBLELayerInitialized = 0x0002, /**< The Bluez layer has been initialized. */ - kAppRegistered = 0x0004, /**< The CHIPoBLE application has been registered with the Bluez layer. */ - kAdvertisingConfigured = 0x0008, /**< CHIPoBLE advertising has been configured in the Bluez layer. */ + kWebOSBLELayerInitialized = 0x0002, /**< The webOS layer has been initialized. */ + kAppRegistered = 0x0004, /**< The CHIPoBLE application has been registered with the webOS layer. */ + kAdvertisingConfigured = 0x0008, /**< CHIPoBLE advertising has been configured in the webOS layer. */ kAdvertising = 0x0010, /**< The system is currently CHIPoBLE advertising. */ kControlOpInProgress = 0x0020, /**< An async control operation has been issued to the ESP BLE layer. */ kAdvertisingEnabled = 0x0040, /**< The application has enabled CHIPoBLE advertising. */ @@ -183,6 +170,34 @@ class BLEManagerImpl final : public BLEManager, kMaxAdvertismentDataSetSize = 31 // TODO: verify this }; + struct BLEConnection + { + char * peerAddr; + uint16_t mtu; + bool subscribed; + void * gattCharC1Handle; + void * gattCharC2Handle; + bool isChipDevice; + }; + + bool SendWriteRequestToWebOS(void * bleConnObj, const uint8_t * svcId, const uint8_t * charId, const uint8_t * pBuf, + uint32_t pBufDataLen); + bool SubscribeCharacteristicToWebOS(void * bleConnObj, const uint8_t * svcId, const uint8_t * charId); + + void InitConnectionData(void); + void AddConnectionData(const char * remoteAddr); + + void HandleConnectionEvent(bool connected, const char * remoteAddress); + + static gboolean _BleInitialize(void * userData); + static bool gattMonitorCharateristicsCb(LSHandle * sh, LSMessage * message, void * userData); + static bool gattWriteDescriptorValueCb(LSHandle * sh, LSMessage * message, void * userData); + static bool gattWriteValueCb(LSHandle * sh, LSMessage * message, void * ctx); + static bool gattGetServiceCb(LSHandle * sh, LSMessage * message, void * ctx); + static bool gattConnectCb(LSHandle * sh, LSMessage * message, void * ctx); + static gboolean ConnectChipThing(gpointer userData); + void ConnectHandler(const char * address); + CHIP_ERROR StartBLEAdvertising(); CHIP_ERROR StopBLEAdvertising(); @@ -194,13 +209,18 @@ class BLEManagerImpl final : public BLEManager, void CleanScanConfig(); CHIPoBLEServiceMode mServiceMode; - BLEAdvConfig mBLEAdvConfig; BLEScanConfig mBLEScanConfig; BitFlags mFlags; char mDeviceName[kMaxDeviceNameLength + 1]; - bool mIsCentral = false; - BluezEndpoint * mpEndpoint = nullptr; + bool mIsCentral = false; std::unique_ptr mDeviceScanner; + + GMainContext * mMainContext = nullptr; + LSHandle * mLSHandle = nullptr; + char * mRemoteAddress = nullptr; + char * mClientId = nullptr; + + GHashTable * mConnectionMap = nullptr; }; /** diff --git a/src/platform/webos/BUILD.gn b/src/platform/webos/BUILD.gn index f0f0fac70f0fdf..50f8f571c928c4 100644 --- a/src/platform/webos/BUILD.gn +++ b/src/platform/webos/BUILD.gn @@ -59,24 +59,6 @@ static_library("webos") { sources = [ "../DeviceSafeQueue.cpp", "../DeviceSafeQueue.h", - "../Linux/CHIPLinuxStorage.cpp", - "../Linux/CHIPLinuxStorage.h", - "../Linux/CHIPLinuxStorageIni.cpp", - "../Linux/CHIPLinuxStorageIni.h", - "../Linux/ConfigurationManagerImpl.cpp", - "../Linux/ConfigurationManagerImpl.h", - "../Linux/ConnectivityUtils.cpp", - "../Linux/ConnectivityUtils.h", - "../Linux/DiagnosticDataProviderImpl.cpp", - "../Linux/DiagnosticDataProviderImpl.h", - "../Linux/InetPlatformConfig.h", - "../Linux/KeyValueStoreManagerImpl.cpp", - "../Linux/KeyValueStoreManagerImpl.h", - "../Linux/PlatformManagerImpl.cpp", - "../Linux/PlatformManagerImpl.h", - "../Linux/PosixConfig.cpp", - "../Linux/PosixConfig.h", - "../Linux/SystemTimeSupport.cpp", "../SingletonConfigurationManager.cpp", "BLEManagerImpl.cpp", "BLEManagerImpl.h", @@ -84,17 +66,45 @@ static_library("webos") { "CHIPDevicePlatformConfig.h", "CHIPDevicePlatformEvent.h", "CHIPPlatformConfig.h", + "CHIPWebOSStorage.cpp", + "CHIPWebOSStorage.h", + "CHIPWebOSStorageIni.cpp", + "CHIPWebOSStorageIni.h", + "ChipDeviceScanner.cpp", + "ChipDeviceScanner.h", + "ConfigurationManagerImpl.cpp", + "ConfigurationManagerImpl.h", "ConnectivityManagerImpl.cpp", "ConnectivityManagerImpl.h", + "ConnectivityUtils.cpp", + "ConnectivityUtils.h", + "DeviceInfoProviderImpl.cpp", + "DeviceInfoProviderImpl.h", "DeviceNetworkProvisioningDelegateImpl.cpp", "DeviceNetworkProvisioningDelegateImpl.h", + "DiagnosticDataProviderImpl.cpp", + "DiagnosticDataProviderImpl.h", + "InetPlatformConfig.h", + "KeyValueStoreManagerImpl.cpp", + "KeyValueStoreManagerImpl.h", "Logging.cpp", + "MainLoop.cpp", + "MainLoop.h", + "NetworkCommissioningDriver.h", + "NetworkCommissioningThreadDriver.cpp", + "NetworkCommissioningWiFiDriver.cpp", + "PlatformManagerImpl.cpp", + "PlatformManagerImpl.h", + "PosixConfig.cpp", + "PosixConfig.h", "SystemPlatformConfig.h", + "SystemTimeSupport.cpp", ] 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", ] @@ -103,8 +113,8 @@ static_library("webos") { if (chip_mdns != "none") { sources += [ - "../Linux/DnssdImpl.cpp", - "../Linux/DnssdImpl.h", + "DnssdImpl.cpp", + "DnssdImpl.h", ] deps += [ "${chip_root}/src/lib/dnssd:platform_header" ] @@ -116,6 +126,16 @@ static_library("webos") { ] } + if (chip_enable_openthread) { + sources += [ + "GlibTypeDeleter.h", + "ThreadStackManagerImpl.cpp", + "ThreadStackManagerImpl.h", + ] + + public_deps += [ "dbus/openthread" ] + } + if (chip_enable_wifi) { # TBD: Use webOS specific Wi-Fi resource #public_deps += [ "dbus/wpa" ] @@ -125,4 +145,10 @@ static_library("webos") { # TBD: Use webOS specific Bluetooth resource #public_deps += [ "dbus/bluez" ] } + + libs = [ + "luna-service2", + "pbnjson_c", + "pbnjson_cpp", + ] } diff --git a/src/platform/webos/BlePlatformConfig.h b/src/platform/webos/BlePlatformConfig.h index 97ebdb565f6b05..bc2baf1c6b8aa4 100644 --- a/src/platform/webos/BlePlatformConfig.h +++ b/src/platform/webos/BlePlatformConfig.h @@ -27,6 +27,7 @@ namespace chip { namespace DeviceLayer { namespace Internal { +struct BluezConnection; } // namespace Internal } // namespace DeviceLayer } // namespace chip diff --git a/src/platform/webos/CHIPDevicePlatformConfig.h b/src/platform/webos/CHIPDevicePlatformConfig.h index 2abb98fe27cb56..3b37c5ccfcf1ad 100644 --- a/src/platform/webos/CHIPDevicePlatformConfig.h +++ b/src/platform/webos/CHIPDevicePlatformConfig.h @@ -45,7 +45,7 @@ // ========== Platform-specific Configuration ========= -// These are configuration options that are unique to webOs platforms. +// These are configuration options that are unique to webOS platforms. // These can be overridden by the application as needed. /** diff --git a/src/platform/webos/CHIPDevicePlatformEvent.h b/src/platform/webos/CHIPDevicePlatformEvent.h index 69f1ed58bcd410..39a84d7c79adb9 100644 --- a/src/platform/webos/CHIPDevicePlatformEvent.h +++ b/src/platform/webos/CHIPDevicePlatformEvent.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2022 Project CHIP Authors + * Copyright (c) 2020-2022 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. @@ -44,7 +44,18 @@ enum PublicPlatformSpecificEventTypes */ enum InternalPlatformSpecificEventTypes { - /* None currently defined */ + kPlatformWebOSEvent = kRange_InternalPlatformSpecific, + kPlatformWebOSBLECentralConnected, + kPlatformWebOSBLECentralConnectFailed, + kPlatformWebOSBLEWriteComplete, + kPlatformWebOSBLESubscribeOpComplete, + kPlatformWebOSBLEIndicationReceived, + kPlatformWebOSBLEC1WriteEvent, + kPlatformWebOSBLEOutOfBuffersEvent, + kPlatformWebOSBLEPeripheralRegisterAppComplete, + kPlatformWebOSBLEPeripheralAdvConfiguredComplete, + kPlatformWebOSBLEPeripheralAdvStartComplete, + kPlatformWebOSBLEPeripheralAdvStopComplete }; } // namespace DeviceEventType @@ -56,7 +67,48 @@ struct ChipDevicePlatformEvent { union { - /* None currently defined */ + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLECentralConnected; + struct + { + CHIP_ERROR mError; + } BLECentralConnectFailed; + struct + { + BLE_CONNECTION_OBJECT mConnection; + } BLEWriteComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + bool mIsSubscribed; + } BLESubscribeOpComplete; + struct + { + BLE_CONNECTION_OBJECT mConnection; + chip::System::PacketBuffer * mData; + } BLEIndicationReceived; + struct + { + bool mIsSuccess; + void * mpAppstate; + } BLEPeripheralRegisterAppComplete; + struct + { + bool mIsSuccess; + void * mpAppstate; + } BLEPeripheralAdvConfiguredComplete; + struct + { + bool mIsSuccess; + void * mpAppstate; + } BLEPeripheralAdvStartComplete; + struct + { + bool mIsSuccess; + void * mpAppstate; + } BLEPeripheralAdvStopComplete; }; }; diff --git a/src/platform/webos/CHIPPlatformConfig.h b/src/platform/webos/CHIPPlatformConfig.h index f69f823be2044d..edaad3a9a0d79c 100644 --- a/src/platform/webos/CHIPPlatformConfig.h +++ b/src/platform/webos/CHIPPlatformConfig.h @@ -37,38 +37,9 @@ using CHIP_CONFIG_PERSISTED_STORAGE_KEY_TYPE = const char *; #define CHIP_CONFIG_VERBOSE_VERIFY_OR_DIE 1 -// ==================== Security Adaptations ==================== - -#define CHIP_CONFIG_USE_OPENSSL_ECC 0 -#define CHIP_CONFIG_USE_MICRO_ECC 0 - -#define CHIP_CONFIG_HASH_IMPLEMENTATION_OPENSSL 0 -#define CHIP_CONFIG_HASH_IMPLEMENTATION_MINCRYPT 1 -#define CHIP_CONFIG_HASH_IMPLEMENTATION_MBEDTLS 0 -#define CHIP_CONFIG_HASH_IMPLEMENTATION_PLATFORM 0 - -#define CHIP_CONFIG_AES_IMPLEMENTATION_OPENSSL 0 -#define CHIP_CONFIG_AES_IMPLEMENTATION_AESNI 0 -#define CHIP_CONFIG_AES_IMPLEMENTATION_MBEDTLS 1 -#define CHIP_CONFIG_AES_IMPLEMENTATION_PLATFORM 0 - -#define CHIP_CONFIG_RNG_IMPLEMENTATION_OPENSSL 0 -#define CHIP_CONFIG_RNG_IMPLEMENTATION_CHIPDRBG 1 -#define CHIP_CONFIG_RNG_IMPLEMENTATION_PLATFORM 0 +#define CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT 1 -#define CHIP_CONFIG_ENABLE_PASE_INITIATOR 0 -#define CHIP_CONFIG_ENABLE_PASE_RESPONDER 1 -#define CHIP_CONFIG_ENABLE_CASE_INITIATOR 1 - -#define CHIP_CONFIG_SUPPORT_PASE_CONFIG0 0 -#define CHIP_CONFIG_SUPPORT_PASE_CONFIG1 0 -#define CHIP_CONFIG_SUPPORT_PASE_CONFIG2 0 -#define CHIP_CONFIG_SUPPORT_PASE_CONFIG3 0 -#define CHIP_CONFIG_SUPPORT_PASE_CONFIG4 1 - -#define CHIP_CONFIG_ENABLE_KEY_EXPORT_INITIATOR 0 - -#define CHIP_CONFIG_ENABLE_PROVISIONING_BUNDLE_SUPPORT 0 +// ==================== Security Adaptations ==================== // ==================== General Configuration Overrides ==================== @@ -90,10 +61,6 @@ using CHIP_CONFIG_PERSISTED_STORAGE_KEY_TYPE = const char *; // ==================== Security Configuration Overrides ==================== -#ifndef CHIP_CONFIG_DEBUG_CERT_VALIDATION -#define CHIP_CONFIG_DEBUG_CERT_VALIDATION 0 -#endif // CHIP_CONFIG_DEBUG_CERT_VALIDATION - #ifndef CHIP_CONFIG_KVS_PATH #define CHIP_CONFIG_KVS_PATH "/tmp/chip_kvs" #endif // CHIP_CONFIG_KVS_PATH diff --git a/src/platform/webos/CHIPWebOSStorage.cpp b/src/platform/webos/CHIPWebOSStorage.cpp new file mode 100644 index 00000000000000..9bf46394b004bf --- /dev/null +++ b/src/platform/webos/CHIPWebOSStorage.cpp @@ -0,0 +1,352 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * This file implements a class for managing client application + * user-editable settings on webOS platform. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +ChipLinuxStorage::ChipLinuxStorage() +{ + mDirty = false; +} + +ChipLinuxStorage::~ChipLinuxStorage() {} + +CHIP_ERROR ChipLinuxStorage::Init(const char * configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + ChipLogDetail(DeviceLayer, "ChipLinuxStorage::Init: Using KVS config file: %s", configFile); + if (mInitialized) + { + ChipLogError(DeviceLayer, "ChipLinuxStorage::Init: Attempt to re-initialize with KVS config file: %s", configFile); + return CHIP_NO_ERROR; + } + + mConfigPath.assign(configFile); + retval = ChipLinuxStorageIni::Init(); + + if (retval == CHIP_NO_ERROR) + { + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + // Create default setting file if not exist. + if (!ifs.good()) + { + mDirty = true; + retval = Commit(); + mDirty = false; + } + } + + if (retval == CHIP_NO_ERROR) + { + retval = ChipLinuxStorageIni::AddConfig(mConfigPath); + } + + mInitialized = true; + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, bool & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + uint32_t result; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, result); + val = (result != 0); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt16Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUIntValue(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValue(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetUInt64Value(key, val); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetStringValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::GetBinaryBlobValue(key, buf, bufSize, outLen); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, bool val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (val) + { + retval = WriteValue(key, static_cast(1)); + } + else + { + retval = WriteValue(key, static_cast(0)); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint16_t val) +{ + char buf[16]; + + snprintf(buf, sizeof(buf), "%u", val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint32_t val) +{ + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValue(const char * key, uint64_t val) +{ + char buf[64]; + + snprintf(buf, sizeof(buf), "%" PRIu64, val); + + return WriteValueStr(key, buf); +} + +CHIP_ERROR ChipLinuxStorage::WriteValueStr(const char * key, const char * val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::AddEntry(key, val); + + mDirty = true; + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::WriteValueBin(const char * key, const uint8_t * data, size_t dataLen) +{ + static const size_t kMaxBlobSize = 5 * 1024; + + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen = 0; + size_t expectedEncodedLen = ((dataLen + 3) * 4) / 3; + + // We only support encoding blobs up to 5kb + if (dataLen > kMaxBlobSize) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + // Compute our expectedEncodedLen + // Allocate just enough space for the encoded data, and the NULL terminator + if (retval == CHIP_NO_ERROR) + { + if (!encodedData.Alloc(expectedEncodedLen + 1)) + { + retval = CHIP_ERROR_NO_MEMORY; + } + } + + // Encode it + if (retval == CHIP_NO_ERROR) + { + // We tested above that dataLen is no more than kMaxBlobSize. + static_assert(kMaxBlobSize < UINT16_MAX, "dataLen won't fit"); + encodedDataLen = Base64Encode(data, static_cast(dataLen), encodedData.Get()); + encodedData[encodedDataLen] = 0; + } + + // Store it + if (retval == CHIP_NO_ERROR) + { + WriteValueStr(key, encodedData.Get()); + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearValue(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveEntry(key); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::ClearAll() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + mLock.lock(); + + retval = ChipLinuxStorageIni::RemoveAll(); + + mLock.unlock(); + + if (retval == CHIP_NO_ERROR) + { + mDirty = true; + retval = Commit(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +bool ChipLinuxStorage::HasValue(const char * key) +{ + bool retval; + + mLock.lock(); + + retval = ChipLinuxStorageIni::HasValue(key); + + mLock.unlock(); + + return retval; +} + +CHIP_ERROR ChipLinuxStorage::Commit() +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if (mDirty && !mConfigPath.empty()) + { + mLock.lock(); + + retval = ChipLinuxStorageIni::CommitConfig(mConfigPath); + + mLock.unlock(); + } + else + { + retval = CHIP_ERROR_WRITE_FAILED; + } + + return retval; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/CHIPWebOSStorage.h b/src/platform/webos/CHIPWebOSStorage.h new file mode 100644 index 00000000000000..55db5149b6b334 --- /dev/null +++ b/src/platform/webos/CHIPWebOSStorage.h @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * This file defines a class for managing client application + * user-editable settings. CHIP settings are partitioned into two + * distinct areas: + * + * 1. immutable / durable: factory parameters (CHIP_DEFAULT_FACTORY_PATH) + * 2. mutable / ephemeral: user parameters (CHIP_DEFAULT_CONFIG_PATH/CHIP_DEFAULT_DATA_PATH) + * + * The ephemeral partition should be erased during factory reset. + * + * ChipLinuxStorage wraps the storage class ChipLinuxStorageIni with mutex. + * + */ + +#pragma once + +#include +#include + +#ifndef FATCONFDIR +#define FATCONFDIR "/tmp" +#endif + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/tmp" +#endif + +#ifndef LOCALSTATEDIR +#define LOCALSTATEDIR "/tmp" +#endif + +#ifndef DEVICEINFODIR +#define DEVICEINFODIR "/tmp" +#endif + +#define CHIP_DEFAULT_FACTORY_PATH \ + FATCONFDIR "/" \ + "chip_factory.ini" +#define CHIP_DEFAULT_CONFIG_PATH \ + SYSCONFDIR "/" \ + "chip_config.ini" +#define CHIP_DEFAULT_DATA_PATH \ + LOCALSTATEDIR "/" \ + "chip_counters.ini" +#define CHIP_DEVICE_INFO_PATH \ + DEVICEINFODIR "/" \ + "chip_device_info.ini" + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage : private ChipLinuxStorageIni +{ +public: + ChipLinuxStorage(); + ~ChipLinuxStorage(); + + CHIP_ERROR Init(const char * configFile); + CHIP_ERROR ReadValue(const char * key, bool & val); + CHIP_ERROR ReadValue(const char * key, uint16_t & val); + CHIP_ERROR ReadValue(const char * key, uint32_t & val); + CHIP_ERROR ReadValue(const char * key, uint64_t & val); + CHIP_ERROR ReadValueStr(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR ReadValueBin(const char * key, uint8_t * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR WriteValue(const char * key, bool val); + CHIP_ERROR WriteValue(const char * key, uint16_t val); + CHIP_ERROR WriteValue(const char * key, uint32_t val); + CHIP_ERROR WriteValue(const char * key, uint64_t val); + CHIP_ERROR WriteValueStr(const char * key, const char * val); + CHIP_ERROR WriteValueBin(const char * key, const uint8_t * data, size_t dataLen); + CHIP_ERROR ClearValue(const char * key); + CHIP_ERROR ClearAll(); + CHIP_ERROR Commit(); + bool HasValue(const char * key); + +private: + std::mutex mLock; + bool mDirty; + std::string mConfigPath; + bool mInitialized = false; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/CHIPWebOSStorageIni.cpp b/src/platform/webos/CHIPWebOSStorageIni.cpp new file mode 100644 index 00000000000000..4ec7e7df018386 --- /dev/null +++ b/src/platform/webos/CHIPWebOSStorageIni.cpp @@ -0,0 +1,393 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store object + * using IniPP on webOS platform. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +CHIP_ERROR ChipLinuxStorageIni::Init() +{ + return RemoveAll(); +} + +CHIP_ERROR ChipLinuxStorageIni::GetDefaultSection(std::map & section) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + auto it = mConfigStore.sections.find("DEFAULT"); + + if (it != mConfigStore.sections.end()) + { + section = mConfigStore.sections["DEFAULT"]; + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::ifstream ifs; + + ifs.open(configFile, std::ifstream::in); + + if (ifs.is_open()) + { + mConfigStore.parse(ifs); + ifs.close(); + } + else + { + ChipLogError(DeviceLayer, "Failed to open config file: %s", configFile.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +// Updating a file atomically and durably on Linux requires: +// 1. Writing to a temporary file +// 2. Sync'ing the temp file to commit updated data +// 3. Using rename() to overwrite the existing file +CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::string tmpPath = configFile + "-XXXXXX"; + + int fd = mkstemp(&tmpPath[0]); + if (fd != -1) + { + std::ofstream ofs; + + ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str()); + + ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc); + mConfigStore.generate(ofs); + + close(fd); + + if (rename(tmpPath.c_str(), configFile.c_str()) == 0) + { + ChipLogError(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str()); + } + else + { + ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno); + retval = CHIP_ERROR_WRITE_FAILED; + } + } + else + { + ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str()); + retval = CHIP_ERROR_OPEN_FAILED; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + auto it = section.find(key); + + if (it != section.end()) + { + if (!inipp::extract(section[key], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUIntValue(const char * key, uint32_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + auto it = section.find(key); + + if (it != section.end()) + { + if (!inipp::extract(section[key], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetUInt64Value(const char * key, uint64_t & val) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + auto it = section.find(key); + + if (it != section.end()) + { + if (!inipp::extract(section[key], val)) + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + std::map section; + + retval = GetDefaultSection(section); + + if (retval == CHIP_NO_ERROR) + { + auto it = section.find(key); + + if (it != section.end()) + { + std::string value; + if (inipp::extract(section[key], value)) + { + size_t len = value.size(); + + if (len > bufSize - 1) + { + outLen = len; + retval = CHIP_ERROR_BUFFER_TOO_SMALL; + } + else + { + outLen = value.copy(buf, len); + buf[outLen] = '\0'; + } + } + else + { + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobDataAndLengths(const char * key, + chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen) +{ + size_t encodedDataPaddingLen = 0; + std::map section; + CHIP_ERROR err = GetDefaultSection(section); + if (err != CHIP_NO_ERROR) + { + return err; + } + + auto it = section.find(key); + if (it == section.end()) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::string value; + + // Compute the expectedDecodedLen + if (!inipp::extract(section[key], value)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + size_t len = value.size(); + if (!encodedData.Alloc(len + 1)) + { + return CHIP_ERROR_NO_MEMORY; + } + encodedDataLen = value.copy(encodedData.Get(), len); + encodedData[encodedDataLen] = '\0'; + + // Check if encoded data was padded. Only "=" or "==" padding combinations are allowed. + if ((encodedDataLen > 0) && (encodedData[encodedDataLen - 1] == '=')) + { + encodedDataPaddingLen++; + if ((encodedDataLen > 1) && (encodedData[encodedDataLen - 2] == '=')) + encodedDataPaddingLen++; + } + + decodedDataLen = ((encodedDataLen - encodedDataPaddingLen) * 3) / 4; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + chip::Platform::ScopedMemoryBuffer encodedData; + size_t encodedDataLen; + size_t expectedDecodedLen = 0; + + retval = GetBinaryBlobDataAndLengths(key, encodedData, encodedDataLen, expectedDecodedLen); + + // Check the size + if (retval != CHIP_NO_ERROR) + { + return retval; + } + + if (expectedDecodedLen > bufSize) + { + decodedDataLen = expectedDecodedLen; + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + if (encodedDataLen > UINT16_MAX) + { + // We can't even pass this length into Base64Decode. + return CHIP_ERROR_DECODE_FAILED; + } + + // Decode it + // Cast is safe because we checked encodedDataLen above. + decodedDataLen = Base64Decode(encodedData.Get(), static_cast(encodedDataLen), decodedData); + if (decodedDataLen == UINT16_MAX || decodedDataLen > expectedDecodedLen) + { + return CHIP_ERROR_DECODE_FAILED; + } + + return CHIP_NO_ERROR; +} + +bool ChipLinuxStorageIni::HasValue(const char * key) +{ + std::map section; + + if (GetDefaultSection(section) != CHIP_NO_ERROR) + return false; + + auto it = section.find(key); + + return it != section.end(); +} + +CHIP_ERROR ChipLinuxStorageIni::AddEntry(const char * key, const char * value) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + if ((key != nullptr) && (value != nullptr)) + { + std::map & section = mConfigStore.sections["DEFAULT"]; + section[key] = std::string(value); + } + else + { + ChipLogError(DeviceLayer, "Invalid input argument, failed to add entry"); + retval = CHIP_ERROR_INVALID_ARGUMENT; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveEntry(const char * key) +{ + CHIP_ERROR retval = CHIP_NO_ERROR; + + std::map & section = mConfigStore.sections["DEFAULT"]; + + auto it = section.find(key); + + if (it != section.end()) + { + section.erase(it); + } + else + { + retval = CHIP_ERROR_KEY_NOT_FOUND; + } + + return retval; +} + +CHIP_ERROR ChipLinuxStorageIni::RemoveAll() +{ + mConfigStore.clear(); + + return CHIP_NO_ERROR; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/CHIPWebOSStorageIni.h b/src/platform/webos/CHIPWebOSStorageIni.h new file mode 100644 index 00000000000000..1c6e564c12bf35 --- /dev/null +++ b/src/platform/webos/CHIPWebOSStorageIni.h @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the Configuration key-value store interface + * using IniPP. + * + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorageIni +{ +public: + CHIP_ERROR Init(); + CHIP_ERROR AddConfig(const std::string & configFile); + CHIP_ERROR CommitConfig(const std::string & configFile); + CHIP_ERROR GetUInt16Value(const char * key, uint16_t & val); + CHIP_ERROR GetUIntValue(const char * key, uint32_t & val); + CHIP_ERROR GetUInt64Value(const char * key, uint64_t & val); + CHIP_ERROR GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen); + CHIP_ERROR GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen); + bool HasValue(const char * key); + +protected: + CHIP_ERROR AddEntry(const char * key, const char * value); + CHIP_ERROR RemoveEntry(const char * key); + CHIP_ERROR RemoveAll(); + +private: + CHIP_ERROR GetDefaultSection(std::map & section); + CHIP_ERROR GetBinaryBlobDataAndLengths(const char * key, chip::Platform::ScopedMemoryBuffer & encodedData, + size_t & encodedDataLen, size_t & decodedDataLen); + inipp::Ini mConfigStore; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/ChipDeviceScanner.cpp b/src/platform/webos/ChipDeviceScanner.cpp new file mode 100644 index 00000000000000..d75723efbf5521 --- /dev/null +++ b/src/platform/webos/ChipDeviceScanner.cpp @@ -0,0 +1,375 @@ +/* + * + * Copyright (c) 2022 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 "ChipDeviceScanner.h" + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + +#include +#include +#include + +#include "MainLoop.h" + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { +namespace { + +static unsigned int kScanTimeout = 10000; + +// Default CHIP Scan Timeout in Millisecond +// static unsigned int kScanTimeout = 10000; + +struct GObjectUnref +{ + template + void operator()(T * value) + { + g_object_unref(value); + } +}; + +using GCancellableUniquePtr = std::unique_ptr; +using GDBusObjectManagerUniquePtr = std::unique_ptr; + +} // namespace + +ChipDeviceScanner::ChipDeviceScanner(LSHandle * handle, ChipDeviceScannerDelegate * delegate) : + mLSHandle(handle), mDelegate(delegate) +{} + +ChipDeviceScanner::ChipDeviceScanner(ChipDeviceScannerDelegate * delegate) : mDelegate(delegate) {} + +ChipDeviceScanner::~ChipDeviceScanner() +{ + StopScan(); + + // In case the timeout timer is still active + chip::DeviceLayer::SystemLayer().CancelTimer(TimerExpiredCallback, this); + mDelegate = nullptr; +} + +std::unique_ptr ChipDeviceScanner::Create(LSHandle * handle, ChipDeviceScannerDelegate * delegate) +{ + if (!LSCall(handle, "palm://com.webos.service.bluetooth2/adapter/getStatus", "{}", NULL, NULL, NULL, NULL)) + { + g_print("Failed to call getStatus LSCall\n"); + } + + return std::make_unique(handle, delegate); +} + +std::unique_ptr ChipDeviceScanner::Create(ChipDeviceScannerDelegate * delegate) +{ + return std::make_unique(delegate); +} + +gboolean ChipDeviceScanner::TimerExpiredCb(gpointer userData) +{ + ChipDeviceScanner * self = (ChipDeviceScanner *) userData; + ChipLogProgress(DeviceLayer, "Scan Timer expired!!"); + self->StopChipScan(); + return G_SOURCE_REMOVE; +} + +void ChipDeviceScanner::printFoundChipDevice(const jvalue_ref & scanRecord, const std::string & address) +{ + + int j = 0; + + int scanRecordLength = jarray_size(scanRecord); + + printf("printFoundChipDevice start : scanRecoredLength : %d \n", scanRecordLength); + while (j < scanRecordLength) + { + int32_t l = -1; + int32_t t = -1; + int32_t v0 = -1; + int32_t v1 = -1; + + jvalue_ref lObj = jarray_get(scanRecord, j); + jvalue_ref tObj = jarray_get(scanRecord, j + 1); + jvalue_ref v0Obj = jarray_get(scanRecord, j + 2); + jvalue_ref v1Obj = jarray_get(scanRecord, j + 3); + + jnumber_get_i32(lObj, &l); + jnumber_get_i32(tObj, &t); + jnumber_get_i32(v0Obj, &v0); + jnumber_get_i32(v1Obj, &v1); + + if (t == 22 && v0 == 175 && v1 == 254) + { // 22 = 0x16 175 = 0xAF, 254 = 0xFE + + int32_t disc1 = -1; + int32_t disc2 = -1; + + jvalue_ref disc1Obj = jarray_get(scanRecord, j + 5); + jvalue_ref disc2Obj = jarray_get(scanRecord, j + 6); + + jnumber_get_i32(disc1Obj, &disc1); + jnumber_get_i32(disc2Obj, &disc2); + + // uint16_t discriminator = (disc2 << 8) | disc1; + + int32_t vid1 = -1; + int32_t vid2 = -1; + int32_t pid1 = -1; + int32_t pid2 = -1; + + jvalue_ref vid1Obj = jarray_get(scanRecord, j + 7); + jvalue_ref vid2Obj = jarray_get(scanRecord, j + 8); + jvalue_ref pid1Obj = jarray_get(scanRecord, j + 9); + jvalue_ref pid2Obj = jarray_get(scanRecord, j + 10); + + jnumber_get_i32(vid1Obj, &vid1); + jnumber_get_i32(vid2Obj, &vid2); + jnumber_get_i32(pid1Obj, &pid1); + jnumber_get_i32(pid2Obj, &pid2); + + // int32_t vid = (vid2 << 8) | vid1; + // int32_t pid = (pid2 << 8) | pid1; + + return; + } + j += l + 1; + } +} + +bool ChipDeviceScanner::deviceGetstatusCb(LSHandle * sh, LSMessage * message, void * userData) +{ + ChipDeviceScanner * self = (ChipDeviceScanner *) userData; + + jvalue_ref parsedObj = { 0 }; + jschema_ref input_schema = jschema_parse(j_cstr_to_buffer("{}"), DOMOPT_NOOPT, NULL); + + if (!input_schema) + return false; + + JSchemaInfo schemaInfo; + jschema_info_init(&schemaInfo, input_schema, NULL, NULL); + parsedObj = jdom_parse(j_cstr_to_buffer(LSMessageGetPayload(message)), DOMOPT_NOOPT, &schemaInfo); + jschema_release(&input_schema); + + if (jis_null(parsedObj)) + return true; + + jvalue_ref devicesObj = { 0 }; + + if (jobject_get_exists(parsedObj, J_CSTR_TO_BUF("devices"), &devicesObj)) + { + int devicesLength = jarray_size(devicesObj); + int i = 0; + while (i < devicesLength) + { + jvalue_ref devicesElementObj = jarray_get(devicesObj, i); + + jvalue_ref manufacturerDataObj = { 0 }; + + if (jobject_get_exists(devicesElementObj, J_CSTR_TO_BUF("manufacturerData"), &manufacturerDataObj)) + { + jvalue_ref scanRecordObj = { 0 }; + + if (jobject_get_exists(manufacturerDataObj, J_CSTR_TO_BUF("scanRecord"), &scanRecordObj)) + { + int scanRecordLength = jarray_size(scanRecordObj); + + int j = 0; + while (j < scanRecordLength) + { + jvalue_ref scanRecordElementObj = jarray_get(scanRecordObj, j); + + int32_t scanRecordElement = -1; + + jnumber_get_i32(scanRecordElementObj, &scanRecordElement); + + if (scanRecordElement == 3) + { + int32_t firstByte = -1; + int32_t secondByte = -1; + int32_t thirdByte = -1; + + jvalue_ref firstByteObj = jarray_get(scanRecordObj, j + 1); + jvalue_ref secondByteObj = jarray_get(scanRecordObj, j + 2); + jvalue_ref thirdByteObj = jarray_get(scanRecordObj, j + 3); + + jnumber_get_i32(firstByteObj, &firstByte); + jnumber_get_i32(secondByteObj, &secondByte); + jnumber_get_i32(thirdByteObj, &thirdByte); + + if (firstByte == 3 && secondByte == 246 && thirdByte == 255) + { + jvalue_ref addressObj = { 0 }; + if (jobject_get_exists(devicesElementObj, J_CSTR_TO_BUF("address"), &addressObj)) + { + raw_buffer address_buf = jstring_get(addressObj); + char * address = g_strdup(address_buf.m_str); + jstring_free_buffer(address_buf); + + printFoundChipDevice(scanRecordObj, address); + + self->mDelegate->OnChipDeviceScanned(address); + } + + jvalue_ref nameObj = { 0 }; + if (jobject_get_exists(devicesElementObj, J_CSTR_TO_BUF("name"), &nameObj)) + { + raw_buffer name_buf = jstring_get(nameObj); + char * name = g_strdup(name_buf.m_str); + jstring_free_buffer(name_buf); + + printf("name : %s \n", name); + } + break; + } + else + { + break; + } + } + + j = j + scanRecordElement + 1; + } + } + } + i = i + 1; + } + } + + return true; +} + +bool ChipDeviceScanner::startDiscoveryCb(LSHandle * sh, LSMessage * message, void * userData) +{ + return true; +} + +gboolean ChipDeviceScanner::TriggerScan(GMainLoop * mainLoop, gpointer userData) +{ + ChipDeviceScanner * self = (ChipDeviceScanner *) userData; + int ret = 0; + GSource * idleSource; + + self->mAsyncLoop = mainLoop; + + ret = LSCall(self->mLSHandle, "luna://com.webos.service.bluetooth2/adapter/internal/startDiscovery", + "{\"typeOfDevice\":\"ble\"}", startDiscoveryCb, userData, NULL, NULL); + + ret = LSCall(self->mLSHandle, "luna://com.webos.service.bluetooth2/device/getStatus", "{\"subscribe\":true}", deviceGetstatusCb, + userData, NULL, NULL); + + VerifyOrExit(ret == 1, ChipLogError(DeviceLayer, "bt_adapter_le_start_scan() ret: %d", ret)); + ChipLogProgress(DeviceLayer, "Scan started"); + + // Start Timer + idleSource = g_timeout_source_new(kScanTimeout); + g_source_set_callback(idleSource, TimerExpiredCb, userData, nullptr); + g_source_set_priority(idleSource, G_PRIORITY_HIGH_IDLE); + g_source_attach(idleSource, g_main_loop_get_context(self->mAsyncLoop)); + g_source_unref(idleSource); + return true; + +exit: + return false; +} + +CHIP_ERROR ChipDeviceScanner::StartChipScan(unsigned timeoutMs) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ReturnErrorCodeIf(mIsScanning, CHIP_ERROR_INCORRECT_STATE); + + kScanTimeout = timeoutMs; + + mLSHandle = MainLoop::Instance().mLSHandle; + + // All set to trigger LE Scan + ChipLogProgress(DeviceLayer, "Start CHIP Scan..."); + if (MainLoop::Instance().AsyncRequest(TriggerScan, this) == false) + { + ChipLogError(DeviceLayer, "Failed to trigger Scan..."); + err = CHIP_ERROR_INTERNAL; + goto exit; + } + + mIsScanning = true; // optimistic, to allow all callbacks to check this + return CHIP_NO_ERROR; + +exit: + ChipLogError(DeviceLayer, "Start CHIP Scan could not succeed fully! Stop Scan..."); + StopChipScan(); + // UnRegisterScanFilter(); + return err; +} + +bool ChipDeviceScanner::cancelDiscoveryCb(LSHandle * sh, LSMessage * message, void * userData) +{ + return true; +} + +CHIP_ERROR ChipDeviceScanner::StopChipScan(void) +{ + int ret = 0; + ReturnErrorCodeIf(!mIsScanning, CHIP_ERROR_INCORRECT_STATE); + + ret = LSCall(mLSHandle, "luna://com.webos.service.bluetooth2/dadapter/cancelDiscovery", "{}", cancelDiscoveryCb, this, NULL, + NULL); + + ChipLogError(DeviceLayer, "Stop CHIP scan ret: %d", ret); + + g_main_loop_quit(mAsyncLoop); + ChipLogProgress(DeviceLayer, "CHIP Scanner Async Thread Quit Done..Wait for Thread Windup...!"); + + // Report to Impl class + mDelegate->OnChipScanComplete(); + + mIsScanning = false; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDeviceScanner::StartScan(System::Clock::Timeout timeout) +{ + return CHIP_NO_ERROR; +} + +void ChipDeviceScanner::TimerExpiredCallback(chip::System::Layer * layer, void * appState) +{ + static_cast(appState)->StopScan(); +} + +CHIP_ERROR ChipDeviceScanner::StopScan() +{ + return CHIP_NO_ERROR; +} + +int ChipDeviceScanner::MainLoopStopScan(ChipDeviceScanner * self) +{ + return 0; +} + +int ChipDeviceScanner::MainLoopStartScan(ChipDeviceScanner * self) +{ + return 0; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +#endif // CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE diff --git a/src/platform/webos/ChipDeviceScanner.h b/src/platform/webos/ChipDeviceScanner.h new file mode 100644 index 00000000000000..d3481204c642a5 --- /dev/null +++ b/src/platform/webos/ChipDeviceScanner.h @@ -0,0 +1,103 @@ +/* + * + * Copyright (c) 2022 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 + +#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE + +#include +#include + +#include +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +/// Receives callbacks when chip devices are being scanned +class ChipDeviceScannerDelegate +{ +public: + virtual ~ChipDeviceScannerDelegate() {} + + virtual void OnChipDeviceScanned(char * address) = 0; + + // Called when a scan was completed (stopped or timed out) + virtual void OnScanComplete() = 0; + virtual void OnChipScanComplete() = 0; +}; + +/// Allows scanning for CHIP devices +/// +/// Will perform scan operations and call back whenever a device is discovered. +class ChipDeviceScanner +{ +public: + ChipDeviceScanner(LSHandle * handle, ChipDeviceScannerDelegate * delegate); + ChipDeviceScanner(ChipDeviceScannerDelegate * delegate); + + ChipDeviceScanner(ChipDeviceScanner &&) = default; + ChipDeviceScanner(const ChipDeviceScanner &) = delete; + ChipDeviceScanner & operator=(const ChipDeviceScanner &) = delete; + + ~ChipDeviceScanner(); + + /// Initiate a scan for devices, with the given timeout + CHIP_ERROR StartScan(System::Clock::Timeout timeout); + + CHIP_ERROR StartChipScan(unsigned timeoutMs); + CHIP_ERROR StopChipScan(void); + + /// Stop any currently running scan + CHIP_ERROR StopScan(); + + static std::unique_ptr Create(LSHandle * handle, ChipDeviceScannerDelegate * delegate); + static std::unique_ptr Create(ChipDeviceScannerDelegate * delegate); + +private: + static void printFoundChipDevice(const jvalue_ref & scanRecord, const std::string & address); + static bool deviceGetstatusCb(LSHandle * sh, LSMessage * message, void * ctx); + static bool startDiscoveryCb(LSHandle * sh, LSMessage * message, void * ctx); + static bool cancelDiscoveryCb(LSHandle * sh, LSMessage * message, void * ctx); + + static gboolean TimerExpiredCb(gpointer user_data); + static gboolean TriggerScan(GMainLoop * mainLoop, gpointer userData); + + static void TimerExpiredCallback(chip::System::Layer * layer, void * appState); + static int MainLoopStartScan(ChipDeviceScanner * self); + static int MainLoopStopScan(ChipDeviceScanner * self); + + LSHandle * mLSHandle = nullptr; + ChipDeviceScannerDelegate * mDelegate = nullptr; + gulong mObjectAddedSignal = 0; + gulong mInterfaceChangedSignal = 0; + bool mIsScanning = false; + bool mIsStopping = false; + GMainLoop * mAsyncLoop = nullptr; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip + +#endif // CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE diff --git a/src/platform/webos/ConfigurationManagerImpl.cpp b/src/platform/webos/ConfigurationManagerImpl.cpp new file mode 100644 index 00000000000000..a302ca0082c24b --- /dev/null +++ b/src/platform/webos/ConfigurationManagerImpl.cpp @@ -0,0 +1,404 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Provides the implementation of the Device Layer ConfigurationManager object + * for webOS platforms. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { + +using namespace ::chip::DeviceLayer::Internal; + +ConfigurationManagerImpl & ConfigurationManagerImpl::GetDefaultInstance() +{ + static ConfigurationManagerImpl sInstance; + return sInstance; +} + +CHIP_ERROR ConfigurationManagerImpl::Init() +{ + CHIP_ERROR err; + uint32_t rebootCount; + + // Force initialization of NVS namespaces if they doesn't already exist. + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipFactory); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipConfig); + SuccessOrExit(err); + err = PosixConfig::EnsureNamespace(PosixConfig::kConfigNamespace_ChipCounters); + SuccessOrExit(err); + + // Initialize the generic implementation base class. + err = Internal::GenericConfigurationManagerImpl::Init(); + SuccessOrExit(err); + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_VendorId)) + { + err = StoreVendorId(CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_ProductId)) + { + err = StoreProductId(CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID); + SuccessOrExit(err); + } + + if (PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_RebootCount)) + { + err = GetRebootCount(rebootCount); + SuccessOrExit(err); + + err = StoreRebootCount(rebootCount + 1); + SuccessOrExit(err); + } + else + { + // The first boot after factory reset of the Node. + err = StoreRebootCount(1); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_TotalOperationalHours)) + { + err = StoreTotalOperationalHours(0); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kCounterKey_BootReason)) + { + err = StoreBootReason(to_underlying(BootReasonType::kUnspecified)); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_RegulatoryLocation)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationType::kIndoor); + err = WriteConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, location); + SuccessOrExit(err); + } + + if (!PosixConfig::ConfigValueExists(PosixConfig::kConfigKey_LocationCapability)) + { + uint32_t location = to_underlying(chip::app::Clusters::GeneralCommissioning::RegulatoryLocationType::kIndoor); + err = WriteConfigValue(PosixConfig::kConfigKey_LocationCapability, location); + SuccessOrExit(err); + } + + err = CHIP_NO_ERROR; + +exit: + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::GetPrimaryWiFiMACAddress(uint8_t * buf) +{ + struct ifaddrs * addresses = nullptr; + CHIP_ERROR error = CHIP_NO_ERROR; + bool found = false; + + VerifyOrExit(getifaddrs(&addresses) == 0, error = CHIP_ERROR_INTERNAL); + for (auto addr = addresses; addr != nullptr; addr = addr->ifa_next) + { + if ((addr->ifa_addr) && (addr->ifa_addr->sa_family == AF_PACKET) && strncmp(addr->ifa_name, "lo", IFNAMSIZ) != 0) + { + struct sockaddr_ll * mac = (struct sockaddr_ll *) addr->ifa_addr; + memcpy(buf, mac->sll_addr, mac->sll_halen); + found = true; + break; + } + } + freeifaddrs(addresses); + if (!found) + { + error = CHIP_ERROR_NO_ENDPOINT; + } + +exit: + return error; +} + +bool ConfigurationManagerImpl::CanFactoryReset() +{ + // TODO(#742): query the application to determine if factory reset is allowed. + return true; +} + +void ConfigurationManagerImpl::InitiateFactoryReset() +{ + PlatformMgr().ScheduleWork(DoFactoryReset); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + + CHIP_ERROR err = ReadConfigValue(configKey, value); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) +{ + PosixConfig::Key configKey{ PosixConfig::kConfigNamespace_ChipCounters, key }; + return WriteConfigValue(configKey, value); +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION +CHIP_ERROR ConfigurationManagerImpl::GetWiFiStationSecurityType(WiFiAuthSecurityType & secType) +{ + CHIP_ERROR err; + uint32_t secTypeInt; + + err = ReadConfigValue(PosixConfig::kConfigKey_WiFiStationSecType, secTypeInt); + if (err == CHIP_NO_ERROR) + { + secType = static_cast(secTypeInt); + } + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::UpdateWiFiStationSecurityType(WiFiAuthSecurityType secType) +{ + CHIP_ERROR err; + WiFiAuthSecurityType curSecType; + + if (secType != kWiFiSecurityType_NotSpecified) + { + err = GetWiFiStationSecurityType(curSecType); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND || (err == CHIP_NO_ERROR && secType != curSecType)) + { + uint32_t secTypeInt = static_cast(secType); + err = WriteConfigValue(PosixConfig::kConfigKey_WiFiStationSecType, secTypeInt); + } + SuccessOrExit(err); + } + else + { + err = PosixConfig::ClearConfigValue(PosixConfig::kConfigKey_WiFiStationSecType); + SuccessOrExit(err); + } + +exit: + return err; +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, bool & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint16_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint32_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValue(Key key, uint64_t & val) +{ + return PosixConfig::ReadConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueStr(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return PosixConfig::ReadConfigValueBin(key, buf, bufSize, outLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, bool val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint16_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint32_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValue(Key key, uint64_t val) +{ + return PosixConfig::WriteConfigValue(key, val); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str) +{ + return PosixConfig::WriteConfigValueStr(key, str); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + return PosixConfig::WriteConfigValueStr(key, str, strLen); +} + +CHIP_ERROR ConfigurationManagerImpl::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return PosixConfig::WriteConfigValueBin(key, data, dataLen); +} + +void ConfigurationManagerImpl::RunConfigUnitTest(void) +{ + PosixConfig::RunConfigUnitTest(); +} + +void ConfigurationManagerImpl::DoFactoryReset(intptr_t arg) +{ + CHIP_ERROR err; + + ChipLogProgress(DeviceLayer, "Performing factory reset"); + + err = PosixConfig::FactoryResetConfig(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset configurations: %s", ErrorStr(err)); + } + + err = PosixConfig::FactoryResetCounters(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to factory reset counters: %s", ErrorStr(err)); + } + +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + + ChipLogProgress(DeviceLayer, "Clearing Thread provision"); + ThreadStackMgr().ErasePersistentInfo(); + +#endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD + + // Restart the system. + ChipLogProgress(DeviceLayer, "System restarting (not implemented)"); + // TODO(#742): restart CHIP exe +} + +CHIP_ERROR ConfigurationManagerImpl::GetVendorId(uint16_t & vendorId) +{ + return ReadConfigValue(PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR ConfigurationManagerImpl::GetProductId(uint16_t & productId) +{ + return ReadConfigValue(PosixConfig::kConfigKey_ProductId, productId); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreVendorId(uint16_t vendorId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_VendorId, vendorId); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreProductId(uint16_t productId) +{ + return WriteConfigValue(PosixConfig::kConfigKey_ProductId, productId); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRebootCount(uint32_t & rebootCount) +{ + return ReadConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreRebootCount(uint32_t rebootCount) +{ + return WriteConfigValue(PosixConfig::kCounterKey_RebootCount, rebootCount); +} + +CHIP_ERROR ConfigurationManagerImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + return ReadConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreTotalOperationalHours(uint32_t totalOperationalHours) +{ + return WriteConfigValue(PosixConfig::kCounterKey_TotalOperationalHours, totalOperationalHours); +} + +CHIP_ERROR ConfigurationManagerImpl::GetBootReason(uint32_t & bootReason) +{ + return ReadConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::StoreBootReason(uint32_t bootReason) +{ + return WriteConfigValue(PosixConfig::kCounterKey_BootReason, bootReason); +} + +CHIP_ERROR ConfigurationManagerImpl::GetRegulatoryLocation(uint8_t & location) +{ + uint32_t value = 0; + + CHIP_ERROR err = ReadConfigValue(PosixConfig::kConfigKey_RegulatoryLocation, value); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(value <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + location = static_cast(value); + } + + return err; +} + +CHIP_ERROR ConfigurationManagerImpl::GetLocationCapability(uint8_t & location) +{ + uint32_t value = 0; + + CHIP_ERROR err = ReadConfigValue(PosixConfig::kConfigKey_LocationCapability, value); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(value <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + location = static_cast(value); + } + + return err; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/ConfigurationManagerImpl.h b/src/platform/webos/ConfigurationManagerImpl.h new file mode 100644 index 00000000000000..12cbef9a9c347f --- /dev/null +++ b/src/platform/webos/ConfigurationManagerImpl.h @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2020 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. + */ + +/** + * @file + * Provides an implementation of the ConfigurationManager object + * for webOS platforms. + */ + +#pragma once + +#include "platform/internal/DeviceNetworkInfo.h" +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the ConfigurationManager singleton object for the webOS platform. + */ +class ConfigurationManagerImpl : public Internal::GenericConfigurationManagerImpl +{ +public: + CHIP_ERROR StoreVendorId(uint16_t vendorId); + CHIP_ERROR StoreProductId(uint16_t productId); + + CHIP_ERROR GetVendorId(uint16_t & vendorId) override; + CHIP_ERROR GetProductId(uint16_t & productId) override; + CHIP_ERROR GetRebootCount(uint32_t & rebootCount) override; + CHIP_ERROR StoreRebootCount(uint32_t rebootCount) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR StoreTotalOperationalHours(uint32_t totalOperationalHours) override; + CHIP_ERROR GetBootReason(uint32_t & bootReason) override; + CHIP_ERROR StoreBootReason(uint32_t bootReason) override; + CHIP_ERROR GetRegulatoryLocation(uint8_t & location) override; + CHIP_ERROR GetLocationCapability(uint8_t & location) override; + static ConfigurationManagerImpl & GetDefaultInstance(); + +private: + // ===== Members that implement the ConfigurationManager public interface. + + CHIP_ERROR Init() override; + CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) override; + bool CanFactoryReset() override; + void InitiateFactoryReset() override; + CHIP_ERROR ReadPersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t & value) override; + CHIP_ERROR WritePersistedStorageValue(::chip::Platform::PersistedStorage::Key key, uint32_t value) override; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI_STATION + CHIP_ERROR GetWiFiStationSecurityType(Internal::WiFiAuthSecurityType & secType); + CHIP_ERROR UpdateWiFiStationSecurityType(Internal::WiFiAuthSecurityType secType); +#endif + + // NOTE: Other public interface methods are implemented by GenericConfigurationManagerImpl<>. + CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + + // ===== Members that implement the GenericConfigurationManagerImpl protected interface. + CHIP_ERROR ReadConfigValue(Key key, bool & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint32_t & val) override; + CHIP_ERROR ReadConfigValue(Key key, uint64_t & val) override; + CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) override; + CHIP_ERROR WriteConfigValue(Key key, bool val) override; + CHIP_ERROR WriteConfigValue(Key key, uint32_t val) override; + CHIP_ERROR WriteConfigValue(Key key, uint64_t val) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str) override; + CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen) override; + CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) override; + void RunConfigUnitTest(void) override; + + // ===== Private members reserved for use by this class only. + + static void DoFactoryReset(intptr_t arg); +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/ConnectivityManagerImpl.cpp b/src/platform/webos/ConnectivityManagerImpl.cpp index 6e2d297c7d1051..a58d6255c101fc 100644 --- a/src/platform/webos/ConnectivityManagerImpl.cpp +++ b/src/platform/webos/ConnectivityManagerImpl.cpp @@ -18,13 +18,20 @@ #include +#include #include #include #include +#include +#include +#include +#include #include #include +#include #include +#include #include #include @@ -74,10 +81,60 @@ NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * ConnectivityMa 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) + { + ChipLogProgress(DeviceLayer, "Got Ethernet interface: %s", mEthIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get Ethernet interface"); + mEthIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetEthNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset Ethernet statistic counts"); + } + + // Initialize the generic base classes that require it. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_Init(); +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + if (ConnectivityUtils::GetWiFiInterfaceName(sWiFiIfName, IFNAMSIZ) == CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Got WiFi interface: %s", sWiFiIfName); + } + else + { + ChipLogError(DeviceLayer, "Failed to get WiFi interface"); + sWiFiIfName[0] = '\0'; + } + + if (GetDiagnosticDataProvider().ResetWiFiNetworkDiagnosticsCounts() != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to reset WiFi statistic counts"); + } +#endif + return CHIP_NO_ERROR; } -void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) {} +void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + // Forward the event to the generic base classes as needed. +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + GenericConnectivityManagerImpl_Thread::_OnPlatformEvent(event); +#endif +} #if CHIP_DEVICE_CONFIG_ENABLE_WPA bool ConnectivityManagerImpl::mAssociattionStarted = false; @@ -88,12 +145,29 @@ std::mutex ConnectivityManagerImpl::mWpaSupplicantMutex; ConnectivityManager::WiFiStationMode ConnectivityManagerImpl::_GetWiFiStationMode() { + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + mWiFiStationMode = (mWpaSupplicant.iface != nullptr) ? kWiFiStationMode_Enabled : kWiFiStationMode_Disabled; + } + return mWiFiStationMode; } CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationMode(ConnectivityManager::WiFiStationMode val) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != ConnectivityManager::kWiFiStationMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiStationMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi station mode change: %s -> %s", WiFiStationModeToStr(mWiFiStationMode), + WiFiStationModeToStr(val)); + } + + mWiFiStationMode = val; +exit: + return err; } System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiStationReconnectInterval() @@ -103,17 +177,38 @@ System::Clock::Timeout ConnectivityManagerImpl::_GetWiFiStationReconnectInterval CHIP_ERROR ConnectivityManagerImpl::_SetWiFiStationReconnectInterval(System::Clock::Timeout val) { - return CHIP_ERROR_NOT_IMPLEMENTED; + mWiFiStationReconnectInterval = val; + + return CHIP_NO_ERROR; } bool ConnectivityManagerImpl::_IsWiFiStationEnabled() { - return true; + return GetWiFiStationMode() == kWiFiStationMode_Enabled; } bool ConnectivityManagerImpl::_IsWiFiStationConnected() { - return true; + bool ret = false; + const gchar * state = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationConnected: interface not connected"); + return false; + } + + state = wpa_fi_w1_wpa_supplicant1_interface_get_state(mWpaSupplicant.iface); + if (g_strcmp0(state, "completed") == 0) + { + mConnectivityFlag.Set(ConnectivityFlags::kHaveIPv4InternetConnectivity) + .Set(ConnectivityFlags::kHaveIPv6InternetConnectivity); + ret = true; + } + + return ret; } bool ConnectivityManagerImpl::_IsWiFiStationApplicationControlled() @@ -123,64 +218,698 @@ bool ConnectivityManagerImpl::_IsWiFiStationApplicationControlled() bool ConnectivityManagerImpl::_IsWiFiStationProvisioned() { - return true; + bool ret = false; + const gchar * bss = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _IsWiFiStationProvisioned: interface not connected"); + return false; + } + + bss = wpa_fi_w1_wpa_supplicant1_interface_get_current_bss(mWpaSupplicant.iface); + if (g_str_match_string("BSSs", bss, true)) + { + ret = true; + } + + return ret; } -void ConnectivityManagerImpl::_ClearWiFiStationProvision() {} +void ConnectivityManagerImpl::_ClearWiFiStationProvision() +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: _ClearWiFiStationProvision: interface not connected"); + return; + } + + if (mWiFiStationMode != kWiFiStationMode_ApplicationControlled) + { + GError * err = nullptr; + wpa_fi_w1_wpa_supplicant1_interface_call_remove_all_networks_sync(mWpaSupplicant.iface, nullptr, &err); + + if (err != nullptr) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to remove all networks with error: %s", + err ? err->message : "unknown error"); + g_error_free(err); + } + } +} bool ConnectivityManagerImpl::_CanStartWiFiScan() { - return true; + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED && + mWpaSupplicant.scanState == GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + + return ret; } CHIP_ERROR ConnectivityManagerImpl::_SetWiFiAPMode(WiFiAPMode val) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrExit(val != kWiFiAPMode_NotSupported, err = CHIP_ERROR_INVALID_ARGUMENT); + + if (mWiFiAPMode != val) + { + ChipLogProgress(DeviceLayer, "WiFi AP mode change: %s -> %s", WiFiAPModeToStr(mWiFiAPMode), WiFiAPModeToStr(val)); + mWiFiAPMode = val; + + DeviceLayer::SystemLayer().ScheduleWork(DriveAPState, nullptr); + } + +exit: + return err; } -void ConnectivityManagerImpl::_DemandStartWiFiAP() {} +void ConnectivityManagerImpl::_DemandStartWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP"); + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + DeviceLayer::SystemLayer().ScheduleWork(DriveAPState, nullptr); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand start WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} -void ConnectivityManagerImpl::_StopOnDemandWiFiAP() {} +void ConnectivityManagerImpl::_StopOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP"); + mLastAPDemandTime = System::Clock::kZero; + DeviceLayer::SystemLayer().ScheduleWork(DriveAPState, nullptr); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: Demand stop WiFi AP ignored, mode: %s", WiFiAPModeToStr(mWiFiAPMode)); + } +} -void ConnectivityManagerImpl::_MaintainOnDemandWiFiAP() {} +void ConnectivityManagerImpl::_MaintainOnDemandWiFiAP() +{ + if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + if (mWiFiAPState == kWiFiAPState_Active) + { + mLastAPDemandTime = System::SystemClock().GetMonotonicTimestamp(); + } + } +} -void ConnectivityManagerImpl::_SetWiFiAPIdleTimeout(System::Clock::Timeout val) {} +void ConnectivityManagerImpl::_SetWiFiAPIdleTimeout(System::Clock::Timeout val) +{ + mWiFiAPIdleTimeout = val; + DeviceLayer::SystemLayer().ScheduleWork(DriveAPState, nullptr); +} + +void ConnectivityManagerImpl::UpdateNetworkStatus() +{ + Network configuredNetwork; + + VerifyOrReturn(IsWiFiStationEnabled() && mpStatusChangeCallback != nullptr); + + CHIP_ERROR err = GetConfiguredNetwork(configuredNetwork); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // If we have already connected to the WiFi AP, then return null to indicate a success state. + if (IsWiFiStationConnected()) + { + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kSuccess, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), NullOptional); + return; + } + + mpStatusChangeCallback->OnNetworkingStatusChange( + Status::kUnknownError, MakeOptional(ByteSpan(configuredNetwork.networkID, configuredNetwork.networkIDLen)), + MakeOptional(GetDisconnectReason())); +} void ConnectivityManagerImpl::_OnWpaPropertiesChanged(WpaFiW1Wpa_supplicant1Interface * proxy, GVariant * changed_properties, const gchar * const * invalidated_properties, gpointer user_data) -{} +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (g_variant_n_children(changed_properties) > 0) + { + GVariantIter * iter; + const gchar * key; + GVariant * value; + + WiFiDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetWiFiDiagnosticsDelegate(); + + g_variant_get(changed_properties, "a{sv}", &iter); + + while (g_variant_iter_loop(iter, "{&sv}", &key, &value)) + { + gchar * value_str; + value_str = g_variant_print(value, TRUE); + ChipLogProgress(DeviceLayer, "wpa_supplicant:PropertiesChanged:key:%s -> %s", key, value_str); + + if (g_strcmp0(key, "State") == 0) + { + if (g_strcmp0(value_str, "\'associating\'") == 0) + { + mAssociattionStarted = true; + } + else if (g_strcmp0(value_str, "\'disconnected\'") == 0) + { + gint reason = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + + if (delegate) + { + delegate->OnDisconnectionDetected(reason); + delegate->OnConnectionStatusChanged(static_cast(WiFiConnectionStatus::kConnected)); + } + + if (mAssociattionStarted) + { + uint8_t associationFailureCause = static_cast(AssociationFailureCause::kUnknown); + uint16_t status = WLAN_STATUS_UNSPECIFIED_FAILURE; + + 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(AssociationFailureCause::kAssociationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_assoc_status_code(mWpaSupplicant.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(AssociationFailureCause::kAuthenticationFailed); + status = wpa_fi_w1_wpa_supplicant1_interface_get_auth_status_code(mWpaSupplicant.iface); + break; + default: + break; + } + + delegate->OnAssociationFailureDetected(associationFailureCause, status); + } -void ConnectivityManagerImpl::_OnWpaInterfaceProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) {} + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); -void ConnectivityManagerImpl::_OnWpaBssProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) {} + mAssociattionStarted = false; + } + else if (g_strcmp0(value_str, "\'associated\'") == 0) + { + if (delegate) + { + delegate->OnConnectionStatusChanged(static_cast(WiFiConnectionStatus::kNotConnected)); + } -void ConnectivityManagerImpl::_OnWpaInterfaceReady(GObject * source_object, GAsyncResult * res, gpointer user_data) {} + DeviceLayer::SystemLayer().ScheduleLambda([]() { ConnectivityMgrImpl().UpdateNetworkStatus(); }); + } + } + + g_free(value_str); + } + + g_variant_iter_free(iter); + } +} + +void ConnectivityManagerImpl::_OnWpaInterfaceProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + GError * err = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1Interface * iface = wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus_finish(res, &err); + + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } + + if (iface != nullptr && err == nullptr) + { + mWpaSupplicant.iface = iface; + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + 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 + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant interface proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } + + // We need to stop auto scan or it will block our network scan. + DeviceLayer::SystemLayer().ScheduleLambda([]() { + CHIP_ERROR errInner = StopAutoScan(); + if (errInner != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "wpa_supplicant: Failed to stop auto scan: %s", ErrorStr(errInner)); + } + }); + + if (err != nullptr) + g_error_free(err); +} + +void ConnectivityManagerImpl::_OnWpaBssProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + GError * err = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + WpaFiW1Wpa_supplicant1BSS * bss = wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus_finish(res, &err); + + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + if (bss != nullptr && err == nullptr) + { + mWpaSupplicant.bss = bss; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant bss proxy"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant bss proxy %s: %s", + mWpaSupplicant.interfacePath, err ? err->message : "unknown error"); + } + + if (err != nullptr) + g_error_free(err); +} + +void ConnectivityManagerImpl::_OnWpaInterfaceReady(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + GError * err = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + gboolean result = + wpa_fi_w1_wpa_supplicant1_call_get_interface_finish(mWpaSupplicant.proxy, &mWpaSupplicant.interfacePath, res, &err); + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + mWpaSupplicant.interfacePath, nullptr, _OnWpaInterfaceProxyReady, + nullptr); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + mWpaSupplicant.interfacePath, nullptr, _OnWpaBssProxyReady, nullptr); + } + else + { + GError * error = nullptr; + GVariant * args = nullptr; + GVariantBuilder builder; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: can't find interface %s: %s", sWiFiIfName, + err ? err->message : "unknown error"); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: try to create interface %s", CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "Ifname", g_variant_new_string(CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME)); + args = g_variant_builder_end(&builder); + + result = wpa_fi_w1_wpa_supplicant1_call_create_interface_sync(mWpaSupplicant.proxy, args, &mWpaSupplicant.interfacePath, + nullptr, &error); + + if (result) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface: %s", mWpaSupplicant.interfacePath); + + strncpy(sWiFiIfName, CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, IFNAMSIZ); + sWiFiIfName[IFNAMSIZ - 1] = '\0'; + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + kWpaSupplicantServiceName, mWpaSupplicant.interfacePath, nullptr, + _OnWpaInterfaceProxyReady, nullptr); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + mWpaSupplicant.interfacePath, nullptr, _OnWpaBssProxyReady, nullptr); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create interface %s: %s", + CHIP_DEVICE_CONFIG_WIFI_STATION_IF_NAME, error ? error->message : "unknown error"); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } + } + + if (error != nullptr) + g_error_free(error); + } + + if (err != nullptr) + g_error_free(err); +} void ConnectivityManagerImpl::_OnWpaInterfaceAdded(WpaFiW1Wpa_supplicant1 * proxy, const gchar * path, GVariant * properties, gpointer user_data) -{} +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath) + { + return; + } + + mWpaSupplicant.interfacePath = const_cast(path); + if (mWpaSupplicant.interfacePath) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_GOT_INTERFACE_PATH; + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface added: %s", mWpaSupplicant.interfacePath); + + wpa_fi_w1_wpa_supplicant1_interface_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + mWpaSupplicant.interfacePath, nullptr, _OnWpaInterfaceProxyReady, + nullptr); + + wpa_fi_w1_wpa_supplicant1_bss_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + mWpaSupplicant.interfacePath, nullptr, _OnWpaBssProxyReady, nullptr); + } +} void ConnectivityManagerImpl::_OnWpaInterfaceRemoved(WpaFiW1Wpa_supplicant1 * proxy, const gchar * path, GVariant * properties, gpointer user_data) -{} +{ + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.interfacePath == nullptr) + { + return; + } + + if (g_strcmp0(mWpaSupplicant.interfacePath, path) == 0) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: WiFi interface removed: %s", path); + + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NO_INTERFACE_PATH; + + if (mWpaSupplicant.interfacePath) + { + g_free(mWpaSupplicant.interfacePath); + mWpaSupplicant.interfacePath = nullptr; + } -void ConnectivityManagerImpl::_OnWpaProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) {} + if (mWpaSupplicant.iface) + { + g_object_unref(mWpaSupplicant.iface); + mWpaSupplicant.iface = nullptr; + } -void ConnectivityManagerImpl::StartWiFiManagement() {} + if (mWpaSupplicant.bss) + { + g_object_unref(mWpaSupplicant.bss); + mWpaSupplicant.bss = nullptr; + } + + mWpaSupplicant.scanState = GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + } +} + +void ConnectivityManagerImpl::_OnWpaProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + GError * err = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + mWpaSupplicant.proxy = wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus_finish(res, &err); + if (mWpaSupplicant.proxy != nullptr && err == nullptr) + { + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_CONNECTED; + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to wpa_supplicant proxy"); + + g_signal_connect(mWpaSupplicant.proxy, "interface-added", G_CALLBACK(_OnWpaInterfaceAdded), NULL); + + g_signal_connect(mWpaSupplicant.proxy, "interface-removed", G_CALLBACK(_OnWpaInterfaceRemoved), NULL); + + wpa_fi_w1_wpa_supplicant1_call_get_interface(mWpaSupplicant.proxy, sWiFiIfName, nullptr, _OnWpaInterfaceReady, nullptr); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to create wpa_supplicant proxy %s", + err ? err->message : "unknown error"); + mWpaSupplicant.state = GDBusWpaSupplicant::WPA_NOT_CONNECTED; + } + + if (err != nullptr) + g_error_free(err); +} + +void ConnectivityManagerImpl::StartWiFiManagement() +{ + mConnectivityFlag.ClearAll(); + mWpaSupplicant.state = GDBusWpaSupplicant::INIT; + mWpaSupplicant.scanState = GDBusWpaSupplicant::WIFI_SCANNING_IDLE; + mWpaSupplicant.proxy = nullptr; + mWpaSupplicant.iface = nullptr; + mWpaSupplicant.bss = nullptr; + mWpaSupplicant.interfacePath = nullptr; + mWpaSupplicant.networkPath = nullptr; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: Start WiFi management"); + + wpa_fi_w1_wpa_supplicant1_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kWpaSupplicantServiceName, + kWpaSupplicantObjectPath, nullptr, _OnWpaProxyReady, nullptr); +} bool ConnectivityManagerImpl::IsWiFiManagementStarted() { - return true; + std::lock_guard lock(mWpaSupplicantMutex); + + bool ret = mWpaSupplicant.state == GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED; + + return ret; } -void ConnectivityManagerImpl::DriveAPState() {} +void ConnectivityManagerImpl::DriveAPState() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + WiFiAPState targetState; + + // If the AP interface is not under application control... + if (mWiFiAPMode != kWiFiAPMode_ApplicationControlled) + { + // Determine the target (desired) state for AP interface... + + // The target state is 'NotActive' if the application has expressly disabled the AP interface. + if (mWiFiAPMode == kWiFiAPMode_Disabled) + { + targetState = kWiFiAPState_NotActive; + } + + // The target state is 'Active' if the application has expressly enabled the AP interface. + else if (mWiFiAPMode == kWiFiAPMode_Enabled) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is 'On demand, when no station is available' + // and the station interface is not provisioned or the application has disabled the station + // interface. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision && + (!IsWiFiStationProvisioned() || GetWiFiStationMode() == kWiFiStationMode_Disabled)) + { + targetState = kWiFiAPState_Active; + } + + // The target state is 'Active' if the AP mode is one of the 'On demand' modes and there + // has been demand for the AP within the idle timeout period. + else if (mWiFiAPMode == kWiFiAPMode_OnDemand || mWiFiAPMode == kWiFiAPMode_OnDemand_NoStationProvision) + { + System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp(); + + if (mLastAPDemandTime != System::Clock::kZero && now < (mLastAPDemandTime + mWiFiAPIdleTimeout)) + { + targetState = kWiFiAPState_Active; + + // Compute the amount of idle time before the AP should be deactivated and + // arm a timer to fire at that time. + System::Clock::Timeout apTimeout = (mLastAPDemandTime + mWiFiAPIdleTimeout) - now; + err = DeviceLayer::SystemLayer().StartTimer(apTimeout, DriveAPState, nullptr); + SuccessOrExit(err); + ChipLogProgress(DeviceLayer, "Next WiFi AP timeout in %" PRIu32 " s", + std::chrono::duration_cast(apTimeout).count()); + } + else + { + targetState = kWiFiAPState_NotActive; + } + } + + // Otherwise the target state is 'NotActive'. + else + { + targetState = kWiFiAPState_NotActive; + } + + // If the current AP state does not match the target state... + if (mWiFiAPState != targetState) + { + if (targetState == kWiFiAPState_Active) + { + err = ConfigureWiFiAP(); + SuccessOrExit(err); + + ChangeWiFiAPState(kWiFiAPState_Active); + } + else + { + if (mWpaSupplicant.networkPath) + { + GError * error = nullptr; + + gboolean 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; + ChangeWiFiAPState(kWiFiAPState_NotActive); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to stop AP mode with error: %s", + error ? error->message : "unknown error"); + err = CHIP_ERROR_INTERNAL; + } + + if (error != nullptr) + g_error_free(error); + } + } + } + } + +exit: + if (err != CHIP_NO_ERROR) + { + SetWiFiAPMode(kWiFiAPMode_Disabled); + ChipLogError(DeviceLayer, "Drive AP state failed: %s", ErrorStr(err)); + } +} CHIP_ERROR ConnectivityManagerImpl::ConfigureWiFiAP() { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR ret = CHIP_NO_ERROR; + GError * err = nullptr; + GVariant * args = nullptr; + GVariantBuilder builder; + + uint16_t channel = 1; + uint16_t discriminator = 0; + char ssid[32]; + + channel = ConnectivityUtils::MapChannelToFrequency(kWiFi_BAND_2_4_GHZ, CHIP_DEVICE_CONFIG_WIFI_AP_CHANNEL); + + if (GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator) != CHIP_NO_ERROR) + discriminator = 0; + + snprintf(ssid, 32, "%s%04u", CHIP_DEVICE_CONFIG_WIFI_AP_SSID_PREFIX, discriminator); + + ChipLogProgress(DeviceLayer, "wpa_supplicant: ConfigureWiFiAP, ssid: %s, channel: %d", ssid, channel); + + // Clean up current network if exists + if (mWpaSupplicant.networkPath) + { + g_object_unref(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssid)); + g_variant_builder_add(&builder, "{sv}", "key_mgmt", g_variant_new_string("NONE")); + g_variant_builder_add(&builder, "{sv}", "mode", g_variant_new_int32(2)); + g_variant_builder_add(&builder, "{sv}", "frequency", g_variant_new_int32(channel)); + args = g_variant_builder_end(&builder); + + gboolean result = wpa_fi_w1_wpa_supplicant1_interface_call_add_network_sync(mWpaSupplicant.iface, args, + &mWpaSupplicant.networkPath, nullptr, &err); + + if (result) + { + GError * error = nullptr; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: SSID: %s: %s", ssid, mWpaSupplicant.networkPath); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath, + nullptr, &error); + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: succeeded to start softAP: SSID: %s", ssid); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to start softAP: SSID: %s: %s", ssid, + error ? error->message : "unknown error"); + + ret = CHIP_ERROR_INTERNAL; + } + + if (error != nullptr) + g_error_free(error); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s: %s", ssid, err ? err->message : "unknown error"); + + if (mWpaSupplicant.networkPath) + { + g_object_unref(mWpaSupplicant.networkPath); + mWpaSupplicant.networkPath = nullptr; + } + + ret = CHIP_ERROR_INTERNAL; + } + + if (err != nullptr) + g_error_free(err); + + return ret; } -void ConnectivityManagerImpl::ChangeWiFiAPState(WiFiAPState newState) {} +void ConnectivityManagerImpl::ChangeWiFiAPState(WiFiAPState newState) +{ + if (mWiFiAPState != newState) + { + ChipLogProgress(DeviceLayer, "WiFi AP state change: %s -> %s", WiFiAPStateToStr(mWiFiAPState), WiFiAPStateToStr(newState)); + mWiFiAPState = newState; + } +} void ConnectivityManagerImpl::DriveAPState(::chip::System::Layer * aLayer, void * aAppState) { @@ -191,59 +920,778 @@ CHIP_ERROR ConnectivityManagerImpl::ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * apCallback) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR ret = CHIP_NO_ERROR; + GError * err = nullptr; + GVariant * args = nullptr; + GVariantBuilder builder; + gboolean result; + char ssidStr[kMaxWiFiSSIDLength + 1u] = { 0 }; + char keyStr[kMaxWiFiKeyLength + 1u] = { 0 }; + + VerifyOrReturnError(ssid.size() <= kMaxWiFiSSIDLength, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(credentials.size() <= kMaxWiFiKeyLength, CHIP_ERROR_INVALID_ARGUMENT); + + // 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) {} +void ConnectivityManagerImpl::_ConnectWiFiNetworkAsyncCallback(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ConnectivityManagerImpl * this_ = reinterpret_cast(user_data); + std::unique_ptr attachRes; + std::unique_ptr 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(#14175): 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(#14175): 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) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR ret = CHIP_NO_ERROR; + GError * err = nullptr; + GVariant * args = nullptr; + GVariantBuilder builder; + gboolean result; + + // 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); + g_variant_builder_add(&builder, "{sv}", "ssid", g_variant_new_string(ssid)); + g_variant_builder_add(&builder, "{sv}", "psk", g_variant_new_string(key)); + 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) + { + GError * error = nullptr; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: added network: SSID: %s: %s", ssid, mWpaSupplicant.networkPath); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_select_network_sync(mWpaSupplicant.iface, mWpaSupplicant.networkPath, + nullptr, &error); + if (result) + { + GError * gerror = nullptr; + + ChipLogProgress(DeviceLayer, "wpa_supplicant: connected to network: SSID: %s", ssid); + + result = wpa_fi_w1_wpa_supplicant1_interface_call_save_config_sync(mWpaSupplicant.iface, nullptr, &gerror); + + if (result) + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: save config succeeded!"); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to save config: %s", + gerror ? gerror->message : "unknown error"); + } + + if (gerror != nullptr) + g_error_free(gerror); + + PostNetworkConnect(); + + // Return success as long as the device is connected to the network + ret = CHIP_NO_ERROR; + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to connect to network: SSID: %s: %s", ssid, + error ? error->message : "unknown error"); + + ret = CHIP_ERROR_INTERNAL; + } + + if (error != nullptr) + g_error_free(error); + } + else + { + ChipLogProgress(DeviceLayer, "wpa_supplicant: failed to add network: %s: %s", ssid, 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::PostNetworkConnect() {} +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() { - return CHIP_ERROR_NOT_IMPLEMENTED; + gboolean result; + std::unique_ptr 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) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + // On Linux simulation, we don't have the DBus API to get the BSSID of connected AP. Use mac address + // of local WiFi network card instead. + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + uint8_t macAddress[kMaxHardwareAddrSize]; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_WI_FI) + { + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, macAddress, kMaxHardwareAddrSize) != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get WiFi network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + value = ByteSpan(macAddress, 6); + err = CHIP_NO_ERROR; + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; } CHIP_ERROR ConnectivityManagerImpl::GetWiFiSecurityType(uint8_t & securityType) { - return CHIP_ERROR_NOT_IMPLEMENTED; + const gchar * mode = nullptr; + + std::lock_guard lock(mWpaSupplicantMutex); + + if (mWpaSupplicant.state != GDBusWpaSupplicant::WPA_INTERFACE_CONNECTED) + { + ChipLogError(DeviceLayer, "wpa_supplicant: _GetWiFiSecurityType: interface proxy not connected"); + return CHIP_ERROR_INCORRECT_STATE; + } + + mode = wpa_fi_w1_wpa_supplicant1_interface_get_current_auth_mode(mWpaSupplicant.iface); + ChipLogProgress(DeviceLayer, "wpa_supplicant: current Wi-Fi security type: %s", mode); + + if (strncmp(mode, "WPA-PSK", 7) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_WPA; + } + else if (strncmp(mode, "WPA2-PSK", 8) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_WPA2; + } + else if (strncmp(mode, "WPA2-EAP", 8) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_WPA2; + } + else if (strncmp(mode, "WPA3-PSK", 8) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_WPA3; + } + else if (strncmp(mode, "WEP", 3) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_WEP; + } + else if (strncmp(mode, "NONE", 4) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_NONE; + } + else if (strncmp(mode, "WPA-NONE", 8) == 0) + { + securityType = EMBER_ZCL_SECURITY_TYPE_NONE; + } + else + { + securityType = EMBER_ZCL_SECURITY_TYPE_UNSPECIFIED; + } + + return CHIP_NO_ERROR; } CHIP_ERROR ConnectivityManagerImpl::GetWiFiVersion(uint8_t & wiFiVersion) { - return CHIP_ERROR_NOT_IMPLEMENTED; + // We don't have driect API to get the WiFi version yet, retrun 802.11n on Linux simulation. + wiFiVersion = EMBER_ZCL_WI_FI_VERSION_TYPE_802__11N; + + return CHIP_NO_ERROR; } -CHIP_ERROR ConnectivityManagerImpl::GetConnectedNetwork(NetworkCommissioning::Network & network) +int32_t ConnectivityManagerImpl::GetDisconnectReason() { - return CHIP_ERROR_NOT_IMPLEMENTED; + std::lock_guard lock(mWpaSupplicantMutex); + std::unique_ptr err; + + gint errorValue = wpa_fi_w1_wpa_supplicant1_interface_get_disconnect_reason(mWpaSupplicant.iface); + // wpa_supplicant DBus API: DisconnectReason: The most recent IEEE 802.11 reason code for disconnect. Negative value + // indicates locally generated disconnection. + return errorValue; +} + +CHIP_ERROR ConnectivityManagerImpl::GetConfiguredNetwork(NetworkCommissioning::Network & network) +{ + std::lock_guard lock(mWpaSupplicantMutex); + std::unique_ptr err; + + const gchar * networkPath = wpa_fi_w1_wpa_supplicant1_interface_get_current_network(mWpaSupplicant.iface); + + // wpa_supplicant DBus API: if network path of current network is "/", means no networks is currently selected. + if (strcmp(networkPath, "/") == 0) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::unique_ptr 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); + // 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(network.networkID), CHIP_ERROR_INTERNAL); + ChipLogDetail(DeviceLayer, "Current connected network: %s", ssidStr); + memcpy(network.networkID, ssidStr + 1, length_actual); + network.networkIDLen = length_actual; + return CHIP_NO_ERROR; } CHIP_ERROR ConnectivityManagerImpl::StopAutoScan() { - return CHIP_ERROR_NOT_IMPLEMENTED; + std::lock_guard lock(mWpaSupplicantMutex); + VerifyOrReturnError(mWpaSupplicant.iface != nullptr, CHIP_ERROR_INCORRECT_STATE); + + std::unique_ptr err; + gboolean result; + + ChipLogDetail(DeviceLayer, "wpa_supplicant: disabling auto scan"); + + 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) { - return CHIP_ERROR_NOT_IMPLEMENTED; + std::lock_guard 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 GetBandAndChannelFromFrequency(uint32_t freq) +{ + std::pair ret = std::make_pair(WiFiBand::k2g4, 0); + if (freq <= 2472) + { + ret.second = static_cast((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((freq - 5000) / 5); + } + else if (freq == 5960 || freq == 5980) + { + ret.first = WiFiBand::k5g; + ret.second = static_cast((freq - 5000) / 5); + } + else if (freq >= 5955) + { + ret.first = WiFiBand::k6g; + ret.second = static_cast((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 err; + std::unique_ptr 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 ssid(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(bssProxy), "SSID")); + std::unique_ptr 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(g_variant_get_fixed_array(ssid.get(), &ssidLen, sizeof(guchar))); + bssidBuf = reinterpret_cast(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:%d", 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 wpa(g_dbus_proxy_get_cached_property(G_DBUS_PROXY(proxy), "WPA")); + std::unique_ptr 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(signal); + } + + auto bandInfo = GetBandAndChannelFromFrequency(frequency); + result.wiFiBand = bandInfo.first; + result.channel = bandInfo.second; + result.security.SetRaw(GetNetworkSecurityType(bssProxy)); + return true; } -void ConnectivityManagerImpl::_OnWpaInterfaceScanDone(GObject * source_object, GAsyncResult * res, gpointer user_data) {} +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 * networkScanned = new std::vector(); + 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 iter(const_cast *>(networkScanned)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + + delete const_cast *>(networkScanned); + }); + + g_strfreev(oldBsss); +} #endif // CHIP_DEVICE_CONFIG_ENABLE_WPA diff --git a/src/platform/webos/ConnectivityManagerImpl.h b/src/platform/webos/ConnectivityManagerImpl.h index 8499a9c5fbdc56..dd702d7f995915 100644 --- a/src/platform/webos/ConnectivityManagerImpl.h +++ b/src/platform/webos/ConnectivityManagerImpl.h @@ -36,7 +36,18 @@ #include #endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#include +#include +#include + +#include +#endif + #include +#include +#include namespace chip { namespace Inet { @@ -76,7 +87,7 @@ struct GDBusWpaSupplicant #endif /** - * Concrete implementation of the ConnectivityManager singleton object for webOs platforms. + * Concrete implementation of the ConnectivityManager singleton object for webOS platforms. */ class ConnectivityManagerImpl final : public ConnectivityManager, #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE @@ -103,6 +114,11 @@ class ConnectivityManagerImpl final : public ConnectivityManager, public: #if CHIP_DEVICE_CONFIG_ENABLE_WPA CHIP_ERROR ProvisionWiFiNetwork(const char * ssid, const char * key); + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } CHIP_ERROR ConnectWiFiNetworkAsync(ByteSpan ssid, ByteSpan credentials, NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * connectCallback); void PostNetworkConnect(); @@ -111,10 +127,11 @@ class ConnectivityManagerImpl final : public ConnectivityManager, void StartWiFiManagement(); bool IsWiFiManagementStarted(); + int32_t GetDisconnectReason(); 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 GetConfiguredNetwork(NetworkCommissioning::Network & network); CHIP_ERROR StartWiFiScan(ByteSpan ssid, NetworkCommissioning::WiFiDriver::ScanCallback * callback); #endif @@ -163,6 +180,7 @@ class ConnectivityManagerImpl final : public ConnectivityManager, void _MaintainOnDemandWiFiAP(); System::Clock::Timeout _GetWiFiAPIdleTimeout(); void _SetWiFiAPIdleTimeout(System::Clock::Timeout val); + void UpdateNetworkStatus(); static CHIP_ERROR StopAutoScan(); static void _OnWpaProxyReady(GObject * source_object, GAsyncResult * res, gpointer user_data); @@ -182,6 +200,8 @@ class ConnectivityManagerImpl final : public ConnectivityManager, static BitFlags mConnectivityFlag; static struct GDBusWpaSupplicant mWpaSupplicant; static std::mutex mWpaSupplicantMutex; + + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; #endif // ==================== ConnectivityManager Private Methods ==================== diff --git a/src/platform/webos/ConnectivityUtils.cpp b/src/platform/webos/ConnectivityUtils.cpp new file mode 100644 index 00000000000000..cae0a9e2108ed9 --- /dev/null +++ b/src/platform/webos/ConnectivityUtils.cpp @@ -0,0 +1,646 @@ +/* + * + * 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. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on webOS platforms. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +uint16_t ConnectivityUtils::Map2400MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inChannel >= 1 && inChannel <= 13) + { + frequency = static_cast(2412 + ((inChannel - 1) * 5)); + } + else if (inChannel == 14) + { + frequency = 2484; + } + + return frequency; +} + +uint16_t ConnectivityUtils::Map5000MHz(const uint8_t inChannel) +{ + uint16_t frequency = 0; + + switch (inChannel) + { + case 183: + frequency = 4915; + break; + case 184: + frequency = 4920; + break; + case 185: + frequency = 4925; + break; + case 187: + frequency = 4935; + break; + case 188: + frequency = 4940; + break; + case 189: + frequency = 4945; + break; + case 192: + frequency = 4960; + break; + case 196: + frequency = 4980; + break; + case 7: + frequency = 5035; + break; + case 8: + frequency = 5040; + break; + case 9: + frequency = 5045; + break; + case 11: + frequency = 5055; + break; + case 12: + frequency = 5060; + break; + case 16: + frequency = 5080; + break; + case 34: + frequency = 5170; + break; + case 36: + frequency = 5180; + break; + case 38: + frequency = 5190; + break; + case 40: + frequency = 5200; + break; + case 42: + frequency = 5210; + break; + case 44: + frequency = 5220; + break; + case 46: + frequency = 5230; + break; + case 48: + frequency = 5240; + break; + case 52: + frequency = 5260; + break; + case 56: + frequency = 5280; + break; + case 60: + frequency = 5300; + break; + case 64: + frequency = 5320; + break; + case 100: + frequency = 5500; + break; + case 104: + frequency = 5520; + break; + case 108: + frequency = 5540; + break; + case 112: + frequency = 5560; + break; + case 116: + frequency = 5580; + break; + case 120: + frequency = 5600; + break; + case 124: + frequency = 5620; + break; + case 128: + frequency = 5640; + break; + case 132: + frequency = 5660; + break; + case 136: + frequency = 5680; + break; + case 140: + frequency = 5700; + break; + case 149: + frequency = 5745; + break; + case 153: + frequency = 5765; + break; + case 157: + frequency = 5785; + break; + case 161: + frequency = 5805; + break; + case 165: + frequency = 5825; + break; + } + + return frequency; +} + +uint16_t ConnectivityUtils::MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel) +{ + uint16_t frequency = 0; + + if (inBand == kWiFi_BAND_2_4_GHZ) + { + frequency = Map2400MHz(inChannel); + } + else if (inBand == kWiFi_BAND_5_0_GHZ) + { + frequency = Map5000MHz(inChannel); + } + + return frequency; +} + +uint8_t ConnectivityUtils::MapFrequencyToChannel(const uint16_t frequency) +{ + if (frequency < 2412) + return 0; + + if (frequency < 2484) + return (frequency - 2407) / 5; + + if (frequency == 2484) + return 14; + + return frequency / 5 - 1000; +} + +double ConnectivityUtils::ConvertFrequenceToFloat(const iw_freq * in) +{ + double result = (double) in->m; + + for (int i = 0; i < in->e; i++) + result *= 10; + + return result; +} + +InterfaceType ConnectivityUtils::GetInterfaceConnectionType(const char * ifname) +{ + InterfaceType ret = InterfaceType::EMBER_ZCL_INTERFACE_TYPE_UNSPECIFIED; + int sock = -1; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + ChipLogError(DeviceLayer, "Failed to open socket"); + return InterfaceType::EMBER_ZCL_INTERFACE_TYPE_UNSPECIFIED; + } + + // Test wireless extensions for CONNECTION_WIFI + struct iwreq pwrq = {}; + strncpy(pwrq.ifr_name, ifname, IFNAMSIZ - 1); + + if (ioctl(sock, SIOCGIWNAME, &pwrq) != -1) + { + ret = InterfaceType::EMBER_ZCL_INTERFACE_TYPE_WI_FI; + } + else if ((strncmp(ifname, "en", 2) == 0) || (strncmp(ifname, "eth", 3) == 0)) + { + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + ifr.ifr_data = reinterpret_cast(&ecmd); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + + if (ioctl(sock, SIOCETHTOOL, &ifr) != -1) + ret = InterfaceType::EMBER_ZCL_INTERFACE_TYPE_ETHERNET; + } + + close(sock); + + return ret; +} + +CHIP_ERROR ConnectivityUtils::GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ifname[0] != '\0') + { + struct ifreq req; + + strcpy(req.ifr_name, ifname); + if (ioctl(skfd, SIOCGIFHWADDR, &req) != -1) + { + // Copy 48-bit IEEE MAC Address + VerifyOrReturnError(bufSize >= 6, CHIP_ERROR_BUFFER_TOO_SMALL); + + memset(buf, 0, bufSize); + memcpy(buf, req.ifr_ifru.ifru_hwaddr.sa_data, 6); + err = CHIP_NO_ERROR; + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_WI_FI) + { + strncpy(ifname, ifa->ifa_name, bufSize); + ifname[bufSize - 1] = '\0'; + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiParameter(int skfd, /* Socket to the kernel */ + const char * ifname, /* Device name */ + int request, /* WE ID */ + struct iwreq * pwrq) /* Fixed part of the request */ +{ + /* Set device name */ + strncpy(pwrq->ifr_name, ifname, IFNAMSIZ); + + /* Do the request */ + if (ioctl(skfd, request, pwrq) < 0) + { + return CHIP_ERROR_BAD_REQUEST; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats) +{ + struct iwreq wrq; + + wrq.u.data.pointer = (caddr_t) stats; + wrq.u.data.length = sizeof(struct iw_statistics); + wrq.u.data.flags = 1; /*Clear updated flag */ + strncpy(wrq.ifr_name, ifname, IFNAMSIZ); + + return GetWiFiParameter(skfd, ifname, SIOCGIWSTATS, &wrq); +} + +CHIP_ERROR ConnectivityUtils::GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWFREQ, &wrq) == CHIP_NO_ERROR) + { + double freq = ConvertFrequenceToFloat(&(wrq.u.freq)); + VerifyOrReturnError((freq / 1000000) <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + channelNumber = MapFrequencyToChannel(static_cast(freq / 1000000)); + + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiRssi(const char * ifname, int8_t & rssi) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + struct iw_quality * qual = &stats.qual; + + /* Check if the statistics are in RCPI (IEEE 802.11k) */ + if (qual->updated & IW_QUAL_RCPI) + { /* Deal with signal level in RCPI */ + /* RCPI = int{(Power in dBm +110)*2} for 0dbm > Power > -110dBm */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + double rcpilevel = (qual->level / 2.0) - 110.0; + VerifyOrReturnError(rcpilevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(rcpilevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Check if the statistics are in dBm */ + if (qual->updated & IW_QUAL_DBM) + { /* Deal with signal level in dBm (absolute power measurement) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + int dblevel = qual->level; + /* Implement a range for dBm[-192; 63] */ + if (qual->level >= 64) + dblevel -= 0x100; + + VerifyOrReturnError(dblevel <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(dblevel); + err = CHIP_NO_ERROR; + } + } + else + { /* Deal with signal level as relative value (0 -> max) */ + if (!(qual->updated & IW_QUAL_LEVEL_INVALID)) + { + VerifyOrReturnError(qual->level <= INT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rssi = static_cast(qual->level); + err = CHIP_NO_ERROR; + } + } + } + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iw_statistics stats; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiStats(skfd, ifname, &stats) == CHIP_NO_ERROR) + { + beaconLostCount = stats.miss.beacon; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct iwreq wrq; + int skfd; + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (GetWiFiParameter(skfd, ifname, SIOCGIWRATE, &wrq) == CHIP_NO_ERROR) + { + currentMaxRate = wrq.u.bitrate.value; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthInterfaceName(char * ifname, size_t bufSize) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + /* Walk through linked list, maintaining head pointer so we + can free list later */ + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_ETHERNET) + { + strncpy(ifname, ifa->ifa_name, bufSize); + ifname[bufSize - 1] = '\0'; + err = CHIP_NO_ERROR; + break; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateType & pHYRate) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + int skfd; + uint32_t speed = 0; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + close(skfd); + return CHIP_ERROR_READ_FAILED; + } + + speed = (ecmd.speed_hi << 16) | ecmd.speed; + switch (speed) + { + case 10: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_10_M; + break; + case 100: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_100_M; + break; + case 1000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_1000_M; + break; + case 25000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_2__5_G; + break; + case 5000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_5_G; + break; + case 10000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_10_G; + break; + case 40000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_40_G; + break; + case 100000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_100_G; + break; + case 200000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_200_G; + break; + case 400000: + pHYRate = EmberAfPHYRateType::EMBER_ZCL_PHY_RATE_TYPE_400_G; + break; + default: + ChipLogError(DeviceLayer, "Undefined speed! (%d)\n", speed); + err = CHIP_ERROR_READ_FAILED; + break; + }; + + close(skfd); + + return err; +} + +CHIP_ERROR ConnectivityUtils::GetEthFullDuplex(const char * ifname, bool & fullDuplex) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + + int skfd; + struct ethtool_cmd ecmd = {}; + ecmd.cmd = ETHTOOL_GSET; + struct ifreq ifr = {}; + + ifr.ifr_data = reinterpret_cast(&ecmd); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + + if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + { + ChipLogError(DeviceLayer, "Failed to create a channel to the NET kernel."); + return CHIP_ERROR_OPEN_FAILED; + } + + if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) + { + ChipLogError(DeviceLayer, "Cannot get device settings"); + err = CHIP_ERROR_READ_FAILED; + } + else + { + fullDuplex = ecmd.duplex == DUPLEX_FULL; + err = CHIP_NO_ERROR; + } + + close(skfd); + + return err; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/ConnectivityUtils.h b/src/platform/webos/ConnectivityUtils.h new file mode 100644 index 00000000000000..6b4376c618e7ae --- /dev/null +++ b/src/platform/webos/ConnectivityUtils.h @@ -0,0 +1,66 @@ +/* + * + * 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. + */ + +/** + * @file + * Utilities for accessing parameters of the network interface and the wireless + * statistics(extracted from /proc/net/wireless) on webOS platforms. + */ + +#pragma once + +#include + +#include /* for "caddr_t" et al */ +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static constexpr uint16_t kWiFi_BAND_2_4_GHZ = 2400; +static constexpr uint16_t kWiFi_BAND_5_0_GHZ = 5000; +static constexpr char kWpaSupplicantServiceName[] = "fi.w1.wpa_supplicant1"; +static constexpr char kWpaSupplicantObjectPath[] = "/fi/w1/wpa_supplicant1"; + +class ConnectivityUtils +{ +public: + static uint16_t MapChannelToFrequency(const uint16_t inBand, const uint8_t inChannel); + static uint8_t MapFrequencyToChannel(const uint16_t frequency); + static app::Clusters::GeneralDiagnostics::InterfaceType GetInterfaceConnectionType(const char * ifname); + static CHIP_ERROR GetInterfaceHardwareAddrs(const char * ifname, uint8_t * buf, size_t bufSize); + static CHIP_ERROR GetWiFiInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetWiFiChannelNumber(const char * ifname, uint16_t & channelNumber); + static CHIP_ERROR GetWiFiRssi(const char * ifname, int8_t & rssi); + static CHIP_ERROR GetWiFiBeaconLostCount(const char * ifname, uint32_t & beaconLostCount); + static CHIP_ERROR GetWiFiCurrentMaxRate(const char * ifname, uint64_t & currentMaxRate); + static CHIP_ERROR GetEthInterfaceName(char * ifname, size_t bufSize); + static CHIP_ERROR GetEthPHYRate(const char * ifname, app::Clusters::EthernetNetworkDiagnostics::PHYRateType & pHYRate); + static CHIP_ERROR GetEthFullDuplex(const char * ifname, bool & fullDuplex); + +private: + static uint16_t Map2400MHz(const uint8_t inChannel); + static uint16_t Map5000MHz(const uint8_t inChannel); + static double ConvertFrequenceToFloat(const iw_freq * in); + static CHIP_ERROR GetWiFiParameter(int skfd, const char * ifname, int request, struct iwreq * pwrq); + static CHIP_ERROR GetWiFiStats(int skfd, const char * ifname, struct iw_statistics * stats); +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/DeviceInfoProviderImpl.cpp b/src/platform/webos/DeviceInfoProviderImpl.cpp new file mode 100644 index 00000000000000..de06f831ec1052 --- /dev/null +++ b/src/platform/webos/DeviceInfoProviderImpl.cpp @@ -0,0 +1,364 @@ +/* + * + * Copyright (c) 2022 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 +#include +#include +#include +#include +#include + +#include +#include + +namespace chip { +namespace DeviceLayer { + +namespace { +constexpr TLV::Tag kLabelNameTag = TLV::ContextTag(0); +constexpr TLV::Tag kLabelValueTag = TLV::ContextTag(1); +} // anonymous namespace + +DeviceInfoProviderImpl & DeviceInfoProviderImpl::GetDefaultInstance() +{ + static DeviceInfoProviderImpl sInstance; + return sInstance; +} + +DeviceInfoProvider::FixedLabelIterator * DeviceInfoProviderImpl::IterateFixedLabel(EndpointId endpoint) +{ + return new FixedLabelIteratorImpl(endpoint); +} + +DeviceInfoProviderImpl::FixedLabelIteratorImpl::FixedLabelIteratorImpl(EndpointId endpoint) : mEndpoint(endpoint) +{ + mIndex = 0; +} + +size_t DeviceInfoProviderImpl::FixedLabelIteratorImpl::Count() +{ + // In Linux Simulation, return the size of the hardcoded labelList on all endpoints. + return 4; +} + +bool DeviceInfoProviderImpl::FixedLabelIteratorImpl::Next(FixedLabelType & output) +{ + bool retval = true; + + // In Linux Simulation, use the following hardcoded labelList on all endpoints. + CHIP_ERROR err = CHIP_NO_ERROR; + + const char * labelPtr = nullptr; + const char * valuePtr = nullptr; + + VerifyOrReturnError(mIndex < 4, false); + + ChipLogProgress(DeviceLayer, "Get the fixed label with index:%ld at endpoint:%d", mIndex, mEndpoint); + + switch (mIndex) + { + case 0: + labelPtr = "room"; + valuePtr = "bedroom 2"; + break; + case 1: + labelPtr = "orientation"; + valuePtr = "North"; + break; + case 2: + labelPtr = "floor"; + valuePtr = "2"; + break; + case 3: + labelPtr = "direction"; + valuePtr = "up"; + break; + default: + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + break; + } + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(std::strlen(labelPtr) <= kMaxLabelNameLength, false); + VerifyOrReturnError(std::strlen(valuePtr) <= kMaxLabelValueLength, false); + + Platform::CopyString(mFixedLabelNameBuf, kMaxLabelNameLength + 1, labelPtr); + Platform::CopyString(mFixedLabelValueBuf, kMaxLabelValueLength + 1, valuePtr); + + output.label = CharSpan::fromCharString(mFixedLabelNameBuf); + output.value = CharSpan::fromCharString(mFixedLabelValueBuf); + + mIndex++; + + retval = true; + } + else + { + retval = false; + } + + return retval; +} + +CHIP_ERROR DeviceInfoProviderImpl::SetUserLabelLength(EndpointId endpoint, size_t val) +{ + DefaultStorageKeyAllocator keyAlloc; + + return mStorage->SyncSetKeyValue(keyAlloc.UserLabelLengthKey(endpoint), &val, static_cast(sizeof(val))); +} + +CHIP_ERROR DeviceInfoProviderImpl::GetUserLabelLength(EndpointId endpoint, size_t & val) +{ + DefaultStorageKeyAllocator keyAlloc; + uint16_t len = static_cast(sizeof(val)); + + return mStorage->SyncGetKeyValue(keyAlloc.UserLabelLengthKey(endpoint), &val, len); +} + +CHIP_ERROR DeviceInfoProviderImpl::SetUserLabelAt(EndpointId endpoint, size_t index, const UserLabelType & userLabel) +{ + DefaultStorageKeyAllocator keyAlloc; + uint8_t buf[UserLabelTLVMaxSize()]; + TLV::TLVWriter writer; + writer.Init(buf); + + TLV::TLVType outerType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); + ReturnErrorOnFailure(writer.PutString(kLabelNameTag, userLabel.label)); + ReturnErrorOnFailure(writer.PutString(kLabelValueTag, userLabel.value)); + ReturnErrorOnFailure(writer.EndContainer(outerType)); + + return mStorage->SyncSetKeyValue(keyAlloc.UserLabelIndexKey(endpoint, index), buf, + static_cast(writer.GetLengthWritten())); +} + +DeviceInfoProvider::UserLabelIterator * DeviceInfoProviderImpl::IterateUserLabel(EndpointId endpoint) +{ + return new UserLabelIteratorImpl(*this, endpoint); +} + +DeviceInfoProviderImpl::UserLabelIteratorImpl::UserLabelIteratorImpl(DeviceInfoProviderImpl & provider, EndpointId endpoint) : + mProvider(provider), mEndpoint(endpoint) +{ + size_t total = 0; + + ReturnOnFailure(mProvider.GetUserLabelLength(mEndpoint, total)); + mTotal = total; + mIndex = 0; +} + +bool DeviceInfoProviderImpl::UserLabelIteratorImpl::Next(UserLabelType & output) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnError(mIndex < mTotal, false); + + DefaultStorageKeyAllocator keyAlloc; + uint8_t buf[UserLabelTLVMaxSize()]; + uint16_t len = static_cast(sizeof(buf)); + + err = mProvider.mStorage->SyncGetKeyValue(keyAlloc.UserLabelIndexKey(mEndpoint, mIndex), buf, len); + VerifyOrReturnError(err == CHIP_NO_ERROR, false); + + TLV::ContiguousBufferTLVReader reader; + reader.Init(buf); + err = reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()); + VerifyOrReturnError(err == CHIP_NO_ERROR, false); + + TLV::TLVType containerType; + VerifyOrReturnError(reader.EnterContainer(containerType) == CHIP_NO_ERROR, false); + + chip::CharSpan label; + chip::CharSpan value; + + VerifyOrReturnError(reader.Next(kLabelNameTag) == CHIP_NO_ERROR, false); + VerifyOrReturnError(reader.Get(label) == CHIP_NO_ERROR, false); + + VerifyOrReturnError(reader.Next(kLabelValueTag) == CHIP_NO_ERROR, false); + VerifyOrReturnError(reader.Get(value) == CHIP_NO_ERROR, false); + + VerifyOrReturnError(reader.VerifyEndOfContainer() == CHIP_NO_ERROR, false); + VerifyOrReturnError(reader.ExitContainer(containerType) == CHIP_NO_ERROR, false); + + Platform::CopyString(mUserLabelNameBuf, label); + Platform::CopyString(mUserLabelValueBuf, value); + + output.label = CharSpan::fromCharString(mUserLabelNameBuf); + output.value = CharSpan::fromCharString(mUserLabelValueBuf); + + mIndex++; + + return true; +} + +DeviceInfoProvider::SupportedLocalesIterator * DeviceInfoProviderImpl::IterateSupportedLocales() +{ + return new SupportedLocalesIteratorImpl(); +} + +size_t DeviceInfoProviderImpl::SupportedLocalesIteratorImpl::Count() +{ + // In Linux Simulation, return the size of the hardcoded list of Strings that are valid values for the ActiveLocale. + // {("en-US"), ("de-DE"), ("fr-FR"), ("en-GB"), ("es-ES"), ("zh-CN"), ("it-IT"), ("ja-JP")} + + return 8; +} + +bool DeviceInfoProviderImpl::SupportedLocalesIteratorImpl::Next(CharSpan & output) +{ + bool retval = true; + + // In Linux simulation, return following hardcoded list of Strings that are valid values for the ActiveLocale. + CHIP_ERROR err = CHIP_NO_ERROR; + + const char * activeLocalePtr = nullptr; + + VerifyOrReturnError(mIndex < 8, false); + + switch (mIndex) + { + case 0: + activeLocalePtr = "en-US"; + break; + case 1: + activeLocalePtr = "de-DE"; + break; + case 2: + activeLocalePtr = "fr-FR"; + break; + case 3: + activeLocalePtr = "en-GB"; + break; + case 4: + activeLocalePtr = "es-ES"; + break; + case 5: + activeLocalePtr = "zh-CN"; + break; + case 6: + activeLocalePtr = "it-IT"; + break; + case 7: + activeLocalePtr = "ja-JP"; + break; + default: + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + break; + } + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(std::strlen(activeLocalePtr) <= kMaxActiveLocaleLength, false); + + Platform::CopyString(mActiveLocaleBuf, kMaxActiveLocaleLength + 1, activeLocalePtr); + + output = CharSpan::fromCharString(mActiveLocaleBuf); + + mIndex++; + + retval = true; + } + else + { + retval = false; + } + + return retval; +} + +DeviceInfoProvider::SupportedCalendarTypesIterator * DeviceInfoProviderImpl::IterateSupportedCalendarTypes() +{ + return new SupportedCalendarTypesIteratorImpl(); +} + +size_t DeviceInfoProviderImpl::SupportedCalendarTypesIteratorImpl::Count() +{ + // In Linux Simulation, return the size of the hardcoded list of Strings that are valid values for the Calendar Types. + // {("kBuddhist"), ("kChinese"), ("kCoptic"), ("kEthiopian"), ("kGregorian"), ("kHebrew"), ("kIndian"), ("kJapanese"), + // ("kKorean"), ("kPersian"), ("kTaiwanese"), ("kIslamic")} + + return 12; +} + +bool DeviceInfoProviderImpl::SupportedCalendarTypesIteratorImpl::Next(CalendarType & output) +{ + bool retval = true; + + // In Linux Simulation, return following hardcoded list of Strings that are valid values for the Calendar Types. + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnError(mIndex < 12, false); + + switch (mIndex) + { + case 0: + output = app::Clusters::TimeFormatLocalization::CalendarType::kBuddhist; + break; + case 1: + output = app::Clusters::TimeFormatLocalization::CalendarType::kChinese; + break; + case 2: + output = app::Clusters::TimeFormatLocalization::CalendarType::kCoptic; + break; + case 3: + output = app::Clusters::TimeFormatLocalization::CalendarType::kEthiopian; + break; + case 4: + output = app::Clusters::TimeFormatLocalization::CalendarType::kGregorian; + break; + case 5: + output = app::Clusters::TimeFormatLocalization::CalendarType::kHebrew; + break; + case 6: + output = app::Clusters::TimeFormatLocalization::CalendarType::kIndian; + break; + case 7: + output = app::Clusters::TimeFormatLocalization::CalendarType::kJapanese; + break; + case 8: + output = app::Clusters::TimeFormatLocalization::CalendarType::kKorean; + break; + case 9: + output = app::Clusters::TimeFormatLocalization::CalendarType::kPersian; + break; + case 10: + output = app::Clusters::TimeFormatLocalization::CalendarType::kTaiwanese; + break; + case 11: + output = app::Clusters::TimeFormatLocalization::CalendarType::kIslamic; + break; + default: + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + break; + } + + if (err == CHIP_NO_ERROR) + { + mIndex++; + retval = true; + } + else + { + retval = false; + } + + return retval; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/DeviceInfoProviderImpl.h b/src/platform/webos/DeviceInfoProviderImpl.h new file mode 100644 index 00000000000000..30a4643c48e440 --- /dev/null +++ b/src/platform/webos/DeviceInfoProviderImpl.h @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2022 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 +#include +#include + +namespace chip { +namespace DeviceLayer { + +class DeviceInfoProviderImpl : public DeviceInfoProvider +{ +public: + DeviceInfoProviderImpl() = default; + ~DeviceInfoProviderImpl() override {} + + // Iterators + FixedLabelIterator * IterateFixedLabel(EndpointId endpoint) override; + UserLabelIterator * IterateUserLabel(EndpointId endpoint) override; + SupportedLocalesIterator * IterateSupportedLocales() override; + SupportedCalendarTypesIterator * IterateSupportedCalendarTypes() override; + + static DeviceInfoProviderImpl & GetDefaultInstance(); + +protected: + class FixedLabelIteratorImpl : public FixedLabelIterator + { + public: + FixedLabelIteratorImpl(EndpointId endpoint); + size_t Count() override; + bool Next(FixedLabelType & output) override; + void Release() override { delete this; } + + private: + EndpointId mEndpoint = 0; + size_t mIndex = 0; + char mFixedLabelNameBuf[kMaxLabelNameLength + 1]; + char mFixedLabelValueBuf[kMaxLabelValueLength + 1]; + }; + + class UserLabelIteratorImpl : public UserLabelIterator + { + public: + UserLabelIteratorImpl(DeviceInfoProviderImpl & provider, EndpointId endpoint); + size_t Count() override { return mTotal; } + bool Next(UserLabelType & output) override; + void Release() override { delete this; } + + private: + DeviceInfoProviderImpl & mProvider; + EndpointId mEndpoint = 0; + size_t mIndex = 0; + size_t mTotal = 0; + char mUserLabelNameBuf[kMaxLabelNameLength + 1]; + char mUserLabelValueBuf[kMaxLabelValueLength + 1]; + }; + + class SupportedLocalesIteratorImpl : public SupportedLocalesIterator + { + public: + SupportedLocalesIteratorImpl() = default; + size_t Count() override; + bool Next(CharSpan & output) override; + void Release() override { delete this; } + + private: + size_t mIndex = 0; + char mActiveLocaleBuf[kMaxActiveLocaleLength + 1]; + }; + + class SupportedCalendarTypesIteratorImpl : public SupportedCalendarTypesIterator + { + public: + SupportedCalendarTypesIteratorImpl() = default; + size_t Count() override; + bool Next(CalendarType & output) override; + void Release() override { delete this; } + + private: + size_t mIndex = 0; + }; + + CHIP_ERROR SetUserLabelLength(EndpointId endpoint, size_t val) override; + CHIP_ERROR GetUserLabelLength(EndpointId endpoint, size_t & val) override; + CHIP_ERROR SetUserLabelAt(EndpointId endpoint, size_t index, const UserLabelType & userLabel) override; + +private: + static constexpr size_t UserLabelTLVMaxSize() { return TLV::EstimateStructOverhead(kMaxLabelNameLength, kMaxLabelValueLength); } +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.cpp b/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.cpp index 8faa2822ea1487..11bfe89cf6752e 100644 --- a/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.cpp +++ b/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.cpp @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020 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. @@ -25,7 +25,22 @@ namespace DeviceLayer { CHIP_ERROR DeviceNetworkProvisioningDelegateImpl::_ProvisionWiFiNetwork(const char * ssid, const char * key) { - return CHIP_ERROR_NOT_IMPLEMENTED; + CHIP_ERROR err = CHIP_NO_ERROR; + + ChipLogProgress(NetworkProvisioning, "LinuxNetworkProvisioningDelegate: SSID: %s", ssid); + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + err = ConnectivityMgrImpl().ProvisionWiFiNetwork(ssid, key); +#else + err = CHIP_ERROR_NOT_IMPLEMENTED; +#endif + + if (err != CHIP_NO_ERROR) + { + ChipLogError(NetworkProvisioning, "Failed to connect to WiFi network: %s", chip::ErrorStr(err)); + } + + return err; } } // namespace DeviceLayer diff --git a/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.h b/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.h index 60fd94ec10433b..47c9f0f6e8262c 100644 --- a/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.h +++ b/src/platform/webos/DeviceNetworkProvisioningDelegateImpl.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2020 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. diff --git a/src/platform/webos/DiagnosticDataProviderImpl.cpp b/src/platform/webos/DiagnosticDataProviderImpl.cpp new file mode 100644 index 00000000000000..19125ac0683fa2 --- /dev/null +++ b/src/platform/webos/DiagnosticDataProviderImpl.cpp @@ -0,0 +1,802 @@ +/* + * + * 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. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object + * for webOS platform. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::chip; +using namespace ::chip::TLV; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceLayer::Internal; +using namespace ::chip::app::Clusters::GeneralDiagnostics; + +namespace { + +enum class EthernetStatsCountType +{ + kEthPacketRxCount, + kEthPacketTxCount, + kEthTxErrCount, + kEthCollisionCount, + kEthOverrunCount +}; + +enum class WiFiStatsCountType +{ + kWiFiUnicastPacketRxCount, + kWiFiUnicastPacketTxCount, + kWiFiMulticastPacketRxCount, + kWiFiMulticastPacketTxCount, + kWiFiOverrunCount +}; + +CHIP_ERROR GetEthernetStatsCount(EthernetStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_ETHERNET) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", ifa->ifa_name); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case EthernetStatsCountType::kEthPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthTxErrCount: + count = stats->tx_errors; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthCollisionCount: + count = stats->collisions; + err = CHIP_NO_ERROR; + break; + case EthernetStatsCountType::kEthOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown Ethernet statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR GetWiFiStatsCount(WiFiStatsCountType type, uint64_t & count) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_WI_FI) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", ifa->ifa_name); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + // The usecase of this function is embedded devices,on which we can interact with the WiFi + // driver to get the accurate number of muticast and unicast packets accurately. + // On Linux simulation, we can only get the total packets received, the total bytes transmitted, + // the multicast packets received and receiver ring buff overflow. + + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + switch (type) + { + case WiFiStatsCountType::kWiFiUnicastPacketRxCount: + count = stats->rx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiUnicastPacketTxCount: + count = stats->tx_packets; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketRxCount: + count = stats->multicast; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiMulticastPacketTxCount: + count = 0; + err = CHIP_NO_ERROR; + break; + case WiFiStatsCountType::kWiFiOverrunCount: + count = stats->rx_over_errors; + err = CHIP_NO_ERROR; + break; + default: + ChipLogError(DeviceLayer, "Unknown WiFi statistic metric type"); + break; + } + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +} // namespace + +namespace chip { +namespace DeviceLayer { + +DiagnosticDataProviderImpl & DiagnosticDataProviderImpl::GetDefaultInstance() +{ + static DiagnosticDataProviderImpl sInstance; + return sInstance; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are not being utilized + // by the current running program. + currentHeapFree = mallocInfo.fordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // Get the current amount of heap memory, in bytes, that are being used by + // the current running program. + currentHeapUsed = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) +{ +#ifndef __GLIBC__ + return CHIP_ERROR_NOT_IMPLEMENTED; +#else + struct mallinfo mallocInfo = mallinfo(); + + // The usecase of this function is embedded devices,on which we would need to intercept + // malloc/calloc/free and then record the maximum amount of heap memory,in bytes, that + // has been used by the Node. + // On Linux, since it uses virtual memory, whereby a page of memory could be copied to + // the hard disk, called swap space, and free up that page of memory. So it is impossible + // to know accurately peak physical memory it use. We just return the current heap memory + // being used by the current running program. + currentHeapHighWatermark = mallocInfo.uordblks; + + return CHIP_NO_ERROR; +#endif +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetThreadMetrics(ThreadMetrics ** threadMetricsOut) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + DIR * proc_dir = opendir("/proc/self/task"); + + if (proc_dir == nullptr) + { + ChipLogError(DeviceLayer, "Failed to open current process task directory"); + } + else + { + ThreadMetrics * head = nullptr; + struct dirent * entry; + + /* proc available, iterate through tasks... */ + while ((entry = readdir(proc_dir)) != nullptr) + { + if (entry->d_name[0] == '.') + continue; + + ThreadMetrics * thread = new ThreadMetrics(); + + strncpy(thread->NameBuf, entry->d_name, kMaxThreadNameLength); + thread->NameBuf[kMaxThreadNameLength] = '\0'; + thread->name = CharSpan::fromCharString(thread->NameBuf); + thread->id = atoi(entry->d_name); + + // TODO: Get stack info of each thread + thread->stackFreeCurrent = 0; + thread->stackFreeMinimum = 0; + thread->stackSize = 0; + + thread->Next = head; + head = thread; + } + + closedir(proc_dir); + + *threadMetricsOut = head; + err = CHIP_NO_ERROR; + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseThreadMetrics(ThreadMetrics * threadMetrics) +{ + while (threadMetrics) + { + ThreadMetrics * del = threadMetrics; + threadMetrics = threadMetrics->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetRebootCount(uint16_t & rebootCount) +{ + uint32_t count = 0; + + CHIP_ERROR err = ConfigurationMgr().GetRebootCount(count); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(count <= UINT16_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + rebootCount = static_cast(count); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetUpTime(uint64_t & upTime) +{ + System::Clock::Timestamp currentTime = System::SystemClock().GetMonotonicTimestamp(); + System::Clock::Timestamp startTime = PlatformMgrImpl().GetStartTime(); + + if (currentTime >= startTime) + { + upTime = std::chrono::duration_cast(currentTime - startTime).count(); + return CHIP_NO_ERROR; + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetTotalOperationalHours(uint32_t & totalOperationalHours) +{ + uint64_t upTime = 0; + + if (GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalHours = 0; + if (ConfigurationMgr().GetTotalOperationalHours(totalHours) == CHIP_NO_ERROR) + { + VerifyOrReturnError(upTime / 3600 <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + totalOperationalHours = totalHours + static_cast(upTime / 3600); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_INVALID_TIME; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetBootReason(BootReasonType & bootReason) +{ + uint32_t reason = 0; + + CHIP_ERROR err = ConfigurationMgr().GetBootReason(reason); + + if (err == CHIP_NO_ERROR) + { + VerifyOrReturnError(reason <= UINT8_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + bootReason = static_cast(reason); + } + + return err; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveHardwareFaults(GeneralFaults & hardwareFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following hardware faults statically. + ReturnErrorOnFailure(hardwareFaults.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_RADIO)); + ReturnErrorOnFailure(hardwareFaults.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_SENSOR)); + ReturnErrorOnFailure(hardwareFaults.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_POWER_SOURCE)); + ReturnErrorOnFailure(hardwareFaults.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_USER_INTERFACE_FAULT)); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveRadioFaults(GeneralFaults & radioFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(radioFaults.add(EMBER_ZCL_RADIO_FAULT_TYPE_WI_FI_FAULT)); + ReturnErrorOnFailure(radioFaults.add(EMBER_ZCL_RADIO_FAULT_TYPE_CELLULAR_FAULT)); + ReturnErrorOnFailure(radioFaults.add(EMBER_ZCL_RADIO_FAULT_TYPE_THREAD_FAULT)); + ReturnErrorOnFailure(radioFaults.add(EMBER_ZCL_RADIO_FAULT_TYPE_NFC_FAULT)); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetActiveNetworkFaults(GeneralFaults & networkFaults) +{ +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnErrorOnFailure(networkFaults.add(EMBER_ZCL_NETWORK_FAULT_TYPE_HARDWARE_FAILURE)); + ReturnErrorOnFailure(networkFaults.add(EMBER_ZCL_NETWORK_FAULT_TYPE_NETWORK_JAMMED)); + ReturnErrorOnFailure(networkFaults.add(EMBER_ZCL_NETWORK_FAULT_TYPE_CONNECTION_FAILED)); +#endif + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** netifpp) +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + NetworkInterface * head = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (struct ifaddrs * ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_PACKET) + { + NetworkInterface * ifp = new NetworkInterface(); + + strncpy(ifp->Name, ifa->ifa_name, Inet::InterfaceId::kMaxIfNameLength); + ifp->Name[Inet::InterfaceId::kMaxIfNameLength - 1] = '\0'; + + ifp->name = CharSpan::fromCharString(ifp->Name); + ifp->isOperational = ifa->ifa_flags & IFF_RUNNING; + ifp->type = ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name); + ifp->offPremiseServicesReachableIPv4.SetNull(); + ifp->offPremiseServicesReachableIPv6.SetNull(); + + if (ConnectivityUtils::GetInterfaceHardwareAddrs(ifa->ifa_name, ifp->MacAddress, kMaxHardwareAddrSize) != + CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get network hardware address"); + } + else + { + // Set 48-bit IEEE MAC Address + ifp->hardwareAddress = ByteSpan(ifp->MacAddress, 6); + } + + ifp->Next = head; + head = ifp; + } + } + + *netifpp = head; + err = CHIP_NO_ERROR; + + freeifaddrs(ifaddr); + } + + return err; +} + +void DiagnosticDataProviderImpl::ReleaseNetworkInterfaces(NetworkInterface * netifp) +{ + while (netifp) + { + NetworkInterface * del = netifp; + netifp = netifp->Next; + delete del; + } +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateType & pHYRate) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthPHYRate(ConnectivityMgrImpl().GetEthernetIfName(), pHYRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthFullDuplex(bool & fullDuplex) +{ + if (ConnectivityMgrImpl().GetEthernetIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetEthFullDuplex(ConnectivityMgrImpl().GetEthernetIfName(), fullDuplex); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTimeSinceReset(uint64_t & timeSinceReset) +{ + return GetDiagnosticDataProvider().GetUpTime(timeSinceReset); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketRxCount(uint64_t & packetRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketRxCount, count)); + VerifyOrReturnError(count >= mEthPacketRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetRxCount = count - mEthPacketRxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthPacketTxCount(uint64_t & packetTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthPacketTxCount, count)); + VerifyOrReturnError(count >= mEthPacketTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetTxCount = count - mEthPacketTxCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthTxErrCount(uint64_t & txErrCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthTxErrCount, count)); + VerifyOrReturnError(count >= mEthTxErrCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + txErrCount = count - mEthTxErrCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthCollisionCount(uint64_t & collisionCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthCollisionCount, count)); + VerifyOrReturnError(count >= mEthCollisionCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + collisionCount = count - mEthCollisionCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetEthOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetEthernetStatsCount(EthernetStatsCountType::kEthOverrunCount, count)); + VerifyOrReturnError(count >= mEthOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mEthOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetEthNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_ETHERNET) + { + ChipLogProgress(DeviceLayer, "Found the primary Ethernet interface:%s", ifa->ifa_name); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mEthPacketRxCount = stats->rx_packets; + mEthPacketTxCount = stats->tx_packets; + mEthTxErrCount = stats->tx_errors; + mEthCollisionCount = stats->collisions; + mEthOverrunCount = stats->rx_over_errors; + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiChannelNumber(uint16_t & channelNumber) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiChannelNumber(ConnectivityMgrImpl().GetWiFiIfName(), channelNumber); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiRssi(int8_t & rssi) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiRssi(ConnectivityMgrImpl().GetWiFiIfName(), rssi); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBeaconLostCount(uint32_t & beaconLostCount) +{ + uint32_t count; + + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + ReturnErrorOnFailure(ConnectivityUtils::GetWiFiBeaconLostCount(ConnectivityMgrImpl().GetWiFiIfName(), count)); + VerifyOrReturnError(count >= mBeaconLostCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + beaconLostCount = count - mBeaconLostCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) +{ + if (ConnectivityMgrImpl().GetWiFiIfName() == nullptr) + { + return CHIP_ERROR_READ_FAILED; + } + + return ConnectivityUtils::GetWiFiCurrentMaxRate(ConnectivityMgrImpl().GetWiFiIfName(), currentMaxRate); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiMulticastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketMulticastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketMulticastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetMulticastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketRxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastRxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastRxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastRxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiUnicastPacketTxCount, count)); + VerifyOrReturnError(count >= mPacketUnicastTxCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + count -= mPacketUnicastTxCount; + VerifyOrReturnError(count <= UINT32_MAX, CHIP_ERROR_INVALID_INTEGER_VALUE); + + packetUnicastTxCount = static_cast(count); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiOverrunCount(uint64_t & overrunCount) +{ + uint64_t count; + + ReturnErrorOnFailure(GetWiFiStatsCount(WiFiStatsCountType::kWiFiOverrunCount, count)); + VerifyOrReturnError(count >= mOverrunCount, CHIP_ERROR_INVALID_INTEGER_VALUE); + + overrunCount = count - mOverrunCount; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DiagnosticDataProviderImpl::ResetWiFiNetworkDiagnosticsCounts() +{ + CHIP_ERROR err = CHIP_ERROR_READ_FAILED; + struct ifaddrs * ifaddr = nullptr; + + ReturnErrorOnFailure(GetWiFiBeaconLostCount(mBeaconLostCount)); + + if (getifaddrs(&ifaddr) == -1) + { + ChipLogError(DeviceLayer, "Failed to get network interfaces"); + } + else + { + struct ifaddrs * ifa = nullptr; + + // Walk through linked list, maintaining head pointer so we can free list later. + for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) + { + if (ConnectivityUtils::GetInterfaceConnectionType(ifa->ifa_name) == InterfaceType::EMBER_ZCL_INTERFACE_TYPE_WI_FI) + { + ChipLogProgress(DeviceLayer, "Found the primary WiFi interface:%s", ifa->ifa_name); + break; + } + } + + if (ifa != nullptr) + { + if (ifa->ifa_addr->sa_family == AF_PACKET && ifa->ifa_data != nullptr) + { + struct rtnl_link_stats * stats = (struct rtnl_link_stats *) ifa->ifa_data; + + mPacketMulticastRxCount = stats->multicast; + mPacketMulticastTxCount = 0; + mPacketUnicastRxCount = stats->rx_packets; + mPacketUnicastTxCount = stats->tx_packets; + mOverrunCount = stats->rx_over_errors; + + err = CHIP_NO_ERROR; + } + } + + freeifaddrs(ifaddr); + } + + return err; +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WIFI + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiVersion(uint8_t & wiFiVersion) +{ + return ConnectivityMgrImpl().GetWiFiVersion(wiFiVersion); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiBssId(ByteSpan & value) +{ + return ConnectivityMgrImpl().GetWiFiBssId(value); +} + +CHIP_ERROR DiagnosticDataProviderImpl::GetWiFiSecurityType(uint8_t & securityType) +{ + return ConnectivityMgrImpl().GetWiFiSecurityType(securityType); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/DiagnosticDataProviderImpl.h b/src/platform/webos/DiagnosticDataProviderImpl.h new file mode 100644 index 00000000000000..b67c93f392d365 --- /dev/null +++ b/src/platform/webos/DiagnosticDataProviderImpl.h @@ -0,0 +1,107 @@ +/* + * + * 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. + */ + +/** + * @file + * Provides an implementation of the DiagnosticDataProvider object. + */ + +#pragma once + +#include + +#include + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the DiagnosticDataProvider singleton object for webOS platforms. + */ +class DiagnosticDataProviderImpl : public DiagnosticDataProvider +{ +public: + static DiagnosticDataProviderImpl & GetDefaultInstance(); + + // ===== Methods that implement the DiagnosticDataProvider abstract interface. + + CHIP_ERROR GetCurrentHeapFree(uint64_t & currentHeapFree) override; + CHIP_ERROR GetCurrentHeapUsed(uint64_t & currentHeapUsed) override; + CHIP_ERROR GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark) override; + CHIP_ERROR GetThreadMetrics(ThreadMetrics ** threadMetricsOut) override; + void ReleaseThreadMetrics(ThreadMetrics * threadMetrics) override; + + CHIP_ERROR GetRebootCount(uint16_t & rebootCount) override; + CHIP_ERROR GetUpTime(uint64_t & upTime) override; + CHIP_ERROR GetTotalOperationalHours(uint32_t & totalOperationalHours) override; + CHIP_ERROR GetBootReason(BootReasonType & bootReason) override; + + CHIP_ERROR GetActiveHardwareFaults(GeneralFaults & hardwareFaults) override; + CHIP_ERROR GetActiveRadioFaults(GeneralFaults & radioFaults) override; + CHIP_ERROR GetActiveNetworkFaults(GeneralFaults & networkFaults) override; + + CHIP_ERROR GetNetworkInterfaces(NetworkInterface ** netifpp) override; + void ReleaseNetworkInterfaces(NetworkInterface * netifp) override; + + CHIP_ERROR GetEthPHYRate(app::Clusters::EthernetNetworkDiagnostics::PHYRateType & pHYRate) override; + CHIP_ERROR GetEthFullDuplex(bool & fullDuplex) override; + CHIP_ERROR GetEthTimeSinceReset(uint64_t & timeSinceReset) override; + CHIP_ERROR GetEthPacketRxCount(uint64_t & packetRxCount) override; + CHIP_ERROR GetEthPacketTxCount(uint64_t & packetTxCount) override; + CHIP_ERROR GetEthTxErrCount(uint64_t & txErrCount) override; + CHIP_ERROR GetEthCollisionCount(uint64_t & collisionCount) override; + CHIP_ERROR GetEthOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetEthNetworkDiagnosticsCounts() override; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + CHIP_ERROR GetWiFiChannelNumber(uint16_t & channelNumber) override; + CHIP_ERROR GetWiFiRssi(int8_t & rssi) override; + CHIP_ERROR GetWiFiBeaconLostCount(uint32_t & beaconLostCount) override; + CHIP_ERROR GetWiFiPacketMulticastRxCount(uint32_t & packetMulticastRxCount) override; + CHIP_ERROR GetWiFiPacketMulticastTxCount(uint32_t & packetMulticastTxCount) override; + CHIP_ERROR GetWiFiPacketUnicastRxCount(uint32_t & packetUnicastRxCount) override; + CHIP_ERROR GetWiFiPacketUnicastTxCount(uint32_t & packetUnicastTxCount) override; + CHIP_ERROR GetWiFiCurrentMaxRate(uint64_t & currentMaxRate) override; + CHIP_ERROR GetWiFiOverrunCount(uint64_t & overrunCount) override; + CHIP_ERROR ResetWiFiNetworkDiagnosticsCounts() override; +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + CHIP_ERROR GetWiFiVersion(uint8_t & wiFiVersion) override; + CHIP_ERROR GetWiFiBssId(ByteSpan & value) override; + CHIP_ERROR GetWiFiSecurityType(uint8_t & securityType) override; +#endif + +private: + uint64_t mEthPacketRxCount = 0; + uint64_t mEthPacketTxCount = 0; + uint64_t mEthTxErrCount = 0; + uint64_t mEthCollisionCount = 0; + uint64_t mEthOverrunCount = 0; + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + uint32_t mBeaconLostCount = 0; + uint32_t mPacketMulticastRxCount = 0; + uint32_t mPacketMulticastTxCount = 0; + uint32_t mPacketUnicastRxCount = 0; + uint32_t mPacketUnicastTxCount = 0; + uint64_t mOverrunCount = 0; +#endif +}; + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/DnssdImpl.cpp b/src/platform/webos/DnssdImpl.cpp new file mode 100644 index 00000000000000..3f5b5258c51344 --- /dev/null +++ b/src/platform/webos/DnssdImpl.cpp @@ -0,0 +1,867 @@ +/* + * + * Copyright (c) 2020 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 "DnssdImpl.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using chip::Dnssd::DnssdServiceProtocol; +using chip::Dnssd::kDnssdTypeMaxSize; +using chip::Dnssd::TextEntry; +using chip::System::SocketEvents; +using std::chrono::duration_cast; +using std::chrono::microseconds; +using std::chrono::seconds; +using std::chrono::steady_clock; + +namespace { + +AvahiProtocol ToAvahiProtocol(chip::Inet::IPAddressType addressType) +{ +#if INET_CONFIG_ENABLE_IPV4 + AvahiProtocol protocol; + + switch (addressType) + { + case chip::Inet::IPAddressType::kIPv4: + protocol = AVAHI_PROTO_INET; + break; + case chip::Inet::IPAddressType::kIPv6: + protocol = AVAHI_PROTO_INET6; + break; + default: + protocol = AVAHI_PROTO_UNSPEC; + break; + } + + return protocol; +#else + // We only support IPV6, never tell AVAHI about INET4 or UNSPEC because + // UNSPEC may actually return IPv4 data. + return AVAHI_PROTO_INET6; +#endif +} + +chip::Inet::IPAddressType ToAddressType(AvahiProtocol protocol) +{ + chip::Inet::IPAddressType type; + + switch (protocol) + { +#if INET_CONFIG_ENABLE_IPV4 + case AVAHI_PROTO_INET: + type = chip::Inet::IPAddressType::kIPv4; + break; +#endif + case AVAHI_PROTO_INET6: + type = chip::Inet::IPAddressType::kIPv6; + break; + default: + type = chip::Inet::IPAddressType::kUnknown; + break; + } + + return type; +} + +AvahiWatchEvent ToAvahiWatchEvent(SocketEvents events) +{ + return static_cast((events.Has(chip::System::SocketEventFlags::kRead) ? AVAHI_WATCH_IN : 0) | + (events.Has(chip::System::SocketEventFlags::kWrite) ? AVAHI_WATCH_OUT : 0) | + (events.Has(chip::System::SocketEventFlags::kError) ? AVAHI_WATCH_ERR : 0)); +} + +void AvahiWatchCallbackTrampoline(chip::System::SocketEvents events, intptr_t data) +{ + AvahiWatch * const watch = reinterpret_cast(data); + watch->mPendingIO = ToAvahiWatchEvent(events); + watch->mCallback(watch, watch->mSocket, watch->mPendingIO, watch->mContext); +} + +CHIP_ERROR MakeAvahiStringListFromTextEntries(TextEntry * entries, size_t size, AvahiStringList ** strListOut) +{ + *strListOut = avahi_string_list_new(nullptr, nullptr); + + for (size_t i = 0; i < size; i++) + { + uint8_t buf[chip::Dnssd::kDnssdTextMaxSize]; + size_t offset = static_cast(snprintf(reinterpret_cast(buf), sizeof(buf), "%s=", entries[i].mKey)); + + if (offset + entries[i].mDataSize > sizeof(buf)) + { + avahi_string_list_free(*strListOut); + *strListOut = nullptr; + return CHIP_ERROR_INVALID_ARGUMENT; + } + + memcpy(&buf[offset], entries[i].mData, entries[i].mDataSize); + *strListOut = avahi_string_list_add_arbitrary(*strListOut, buf, offset + entries[i].mDataSize); + } + return CHIP_NO_ERROR; +} + +const char * GetProtocolString(DnssdServiceProtocol protocol) +{ + return protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? "_udp" : "_tcp"; +} + +std::string GetFullType(const char * type, DnssdServiceProtocol protocol) +{ + std::ostringstream typeBuilder; + typeBuilder << type << "." << GetProtocolString(protocol); + return typeBuilder.str(); +} + +} // namespace + +namespace chip { +namespace Dnssd { + +MdnsAvahi MdnsAvahi::sInstance; + +Poller::Poller() +{ + mAvahiPoller.userdata = this; + mAvahiPoller.watch_new = WatchNew; + mAvahiPoller.watch_update = WatchUpdate; + mAvahiPoller.watch_get_events = WatchGetEvents; + mAvahiPoller.watch_free = WatchFree; + + mAvahiPoller.timeout_new = TimeoutNew; + mAvahiPoller.timeout_update = TimeoutUpdate; + mAvahiPoller.timeout_free = TimeoutFree; + + mEarliestTimeout = std::chrono::steady_clock::time_point(); +} + +AvahiWatch * Poller::WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context) +{ + return reinterpret_cast(poller->userdata)->WatchNew(fd, event, callback, context); +} + +AvahiWatch * Poller::WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context) +{ + VerifyOrDie(callback != nullptr && fd >= 0); + + auto watch = std::make_unique(); + watch->mSocket = fd; + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().StartWatchingSocket(fd, &watch->mSocketWatch)); + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().SetCallback(watch->mSocketWatch, AvahiWatchCallbackTrampoline, + reinterpret_cast(watch.get()))); + WatchUpdate(watch.get(), event); + watch->mCallback = callback; + watch->mContext = context; + watch->mPoller = this; + mWatches.emplace_back(std::move(watch)); + + return mWatches.back().get(); +} + +void Poller::WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event) +{ + if (event & AVAHI_WATCH_IN) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingRead(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingRead(watch->mSocketWatch)); + } + if (event & AVAHI_WATCH_OUT) + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().RequestCallbackOnPendingWrite(watch->mSocketWatch)); + } + else + { + LogErrorOnFailure(DeviceLayer::SystemLayerSockets().ClearCallbackOnPendingWrite(watch->mSocketWatch)); + } +} + +AvahiWatchEvent Poller::WatchGetEvents(AvahiWatch * watch) +{ + return watch->mPendingIO; +} + +void Poller::WatchFree(AvahiWatch * watch) +{ + reinterpret_cast(watch->mPoller)->WatchFree(*watch); +} + +void Poller::WatchFree(AvahiWatch & watch) +{ + DeviceLayer::SystemLayerSockets().StopWatchingSocket(&watch.mSocketWatch); + mWatches.erase(std::remove_if(mWatches.begin(), mWatches.end(), + [&watch](const std::unique_ptr & aValue) { return aValue.get() == &watch; }), + mWatches.end()); +} + +AvahiTimeout * Poller::TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context) +{ + VerifyOrDie(poller != nullptr && callback != nullptr); + + return static_cast(poller->userdata)->TimeoutNew(timeout, callback, context); +} + +steady_clock::time_point GetAbsTimeout(const struct timeval * timeout) +{ + steady_clock::time_point now = steady_clock::now(); + steady_clock::time_point absTimeout = now; + + if (timeout != nullptr) + { + absTimeout += seconds(timeout->tv_sec); + absTimeout += microseconds(timeout->tv_usec); + } + + return absTimeout; +} + +AvahiTimeout * Poller::TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context) +{ + mTimers.emplace_back(new AvahiTimeout{ GetAbsTimeout(timeout), callback, timeout != nullptr, context, this }); + AvahiTimeout * timer = mTimers.back().get(); + SystemTimerUpdate(timer); + return timer; +} + +void Poller::TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout) +{ + if (timeout) + { + timer->mAbsTimeout = GetAbsTimeout(timeout); + timer->mEnabled = true; + static_cast(timer->mPoller)->SystemTimerUpdate(timer); + } + else + { + timer->mEnabled = false; + } +} + +void Poller::TimeoutFree(AvahiTimeout * timer) +{ + static_cast(timer->mPoller)->TimeoutFree(*timer); +} + +void Poller::TimeoutFree(AvahiTimeout & timer) +{ + mTimers.erase(std::remove_if(mTimers.begin(), mTimers.end(), + [&timer](const std::unique_ptr & aValue) { return aValue.get() == &timer; }), + mTimers.end()); +} + +void Poller::SystemTimerCallback(System::Layer * layer, void * data) +{ + static_cast(data)->HandleTimeout(); +} + +void Poller::HandleTimeout() +{ + mEarliestTimeout = std::chrono::steady_clock::time_point(); + steady_clock::time_point now = steady_clock::now(); + + AvahiTimeout * earliest = nullptr; + for (auto && timer : mTimers) + { + if (!timer->mEnabled) + { + continue; + } + if (timer->mAbsTimeout <= now) + { + timer->mCallback(timer.get(), timer->mContext); + } + else + { + if ((earliest == nullptr) || (timer->mAbsTimeout < earliest->mAbsTimeout)) + { + earliest = timer.get(); + } + } + } + if (earliest) + { + SystemTimerUpdate(earliest); + } +} + +void Poller::SystemTimerUpdate(AvahiTimeout * timer) +{ + if ((mEarliestTimeout == std::chrono::steady_clock::time_point()) || (timer->mAbsTimeout < mEarliestTimeout)) + { + mEarliestTimeout = timer->mAbsTimeout; + auto delay = std::chrono::duration_cast(steady_clock::now() - mEarliestTimeout); + DeviceLayer::SystemLayer().StartTimer(delay, SystemTimerCallback, this); + } +} + +CHIP_ERROR MdnsAvahi::Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int avahiError = 0; + + VerifyOrExit(initCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(errorCallback != nullptr, error = CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrExit(mClient == nullptr && mGroup == nullptr, error = CHIP_ERROR_INCORRECT_STATE); + mInitCallback = initCallback; + mErrorCallback = errorCallback; + mAsyncReturnContext = context; + mClient = avahi_client_new(mPoller.GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError); + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_OPEN_FAILED); + VerifyOrExit(avahiError == 0, error = CHIP_ERROR_OPEN_FAILED); + +exit: + return error; +} + +CHIP_ERROR MdnsAvahi::Shutdown() +{ + if (mGroup) + { + avahi_entry_group_free(mGroup); + mGroup = nullptr; + } + if (mClient) + { + avahi_client_free(mClient); + mClient = nullptr; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR MdnsAvahi::SetHostname(const char * hostname) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + int avahiRet; + + VerifyOrExit(mClient != nullptr, error = CHIP_ERROR_INCORRECT_STATE); + avahiRet = avahi_client_set_host_name(mClient, hostname); + if (avahiRet == AVAHI_ERR_ACCESS_DENIED) + { + ChipLogError(DeviceLayer, "Cannot set hostname on this system, continue anyway..."); + } + else if (avahiRet != AVAHI_OK && avahiRet != AVAHI_ERR_NO_CHANGE) + { + error = CHIP_ERROR_INTERNAL; + } + +exit: + return error; +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state, void * context) +{ + static_cast(context)->HandleClientState(client, state); +} + +void MdnsAvahi::HandleClientState(AvahiClient * client, AvahiClientState state) +{ + switch (state) + { + case AVAHI_CLIENT_S_RUNNING: + ChipLogProgress(DeviceLayer, "Avahi client registered"); + mClient = client; + mGroup = avahi_entry_group_new(client, HandleGroupState, this); + if (mGroup == nullptr) + { + ChipLogError(DeviceLayer, "Failed to create avahi group: %s", avahi_strerror(avahi_client_errno(client))); + mInitCallback(mAsyncReturnContext, CHIP_ERROR_OPEN_FAILED); + } + else + { + mInitCallback(mAsyncReturnContext, CHIP_NO_ERROR); + } + break; + case AVAHI_CLIENT_FAILURE: + ChipLogError(DeviceLayer, "Avahi client failure"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_REGISTERING: + ChipLogProgress(DeviceLayer, "Avahi re-register required"); + if (mGroup != nullptr) + { + avahi_entry_group_reset(mGroup); + avahi_entry_group_free(mGroup); + } + mGroup = avahi_entry_group_new(client, HandleGroupState, this); + mPublishedServices.clear(); + if (mGroup == nullptr) + { + ChipLogError(DeviceLayer, "Failed to create avahi group: %s", avahi_strerror(avahi_client_errno(client))); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_OPEN_FAILED); + } + else + { + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_FORCED_RESET); + } + break; + case AVAHI_CLIENT_CONNECTING: + ChipLogProgress(DeviceLayer, "Avahi connecting"); + break; + } +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context) +{ + static_cast(context)->HandleGroupState(group, state); +} + +void MdnsAvahi::HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state) +{ + switch (state) + { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + ChipLogProgress(DeviceLayer, "Avahi group established"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + ChipLogError(DeviceLayer, "Avahi group collision"); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_MDNS_COLLISION); + break; + case AVAHI_ENTRY_GROUP_FAILURE: + ChipLogError(DeviceLayer, "Avahi group internal failure %s", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(mGroup)))); + mErrorCallback(mAsyncReturnContext, CHIP_ERROR_INTERNAL); + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +CHIP_ERROR MdnsAvahi::PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context) +{ + std::ostringstream keyBuilder; + std::string key; + std::string type = GetFullType(service.mType, service.mProtocol); + CHIP_ERROR error = CHIP_NO_ERROR; + AvahiStringList * text = nullptr; + AvahiIfIndex interface = + service.mInterface.IsPresent() ? static_cast(service.mInterface.GetPlatformInterface()) : AVAHI_IF_UNSPEC; + + keyBuilder << service.mName << "." << type << service.mPort << "." << interface; + key = keyBuilder.str(); + ChipLogProgress(DeviceLayer, "PublishService %s", key.c_str()); + + if (mPublishedServices.find(key) == mPublishedServices.end()) + { + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); + + mPublishedServices.emplace(key); + VerifyOrExit(avahi_entry_group_add_service_strlst(mGroup, interface, ToAvahiProtocol(service.mAddressType), + static_cast(0), service.mName, type.c_str(), nullptr, + nullptr, service.mPort, text) == 0, + error = CHIP_ERROR_INTERNAL); + for (size_t i = 0; i < service.mSubTypeSize; i++) + { + std::ostringstream sstream; + + sstream << service.mSubTypes[i] << "._sub." << type; + + VerifyOrExit(avahi_entry_group_add_service_subtype(mGroup, interface, ToAvahiProtocol(service.mAddressType), + static_cast(0), service.mName, type.c_str(), + nullptr, sstream.str().c_str()) == 0, + error = CHIP_ERROR_INTERNAL); + } + } + else + { + SuccessOrExit(error = MakeAvahiStringListFromTextEntries(service.mTextEntries, service.mTextEntrySize, &text)); + + VerifyOrExit(avahi_entry_group_update_service_txt_strlst(mGroup, interface, ToAvahiProtocol(service.mAddressType), + static_cast(0), service.mName, type.c_str(), + nullptr, text) == 0, + error = CHIP_ERROR_INTERNAL); + } + + VerifyOrExit(avahi_entry_group_commit(mGroup) == 0, error = CHIP_ERROR_INTERNAL); + +exit: + if (text != nullptr) + { + avahi_string_list_free(text); + } + + // Ideally the callback would be called from `HandleGroupState` when the `AVAHI_ENTRY_GROUP_ESTABLISHED` state + // is received. But the current code use the `userdata` field to pass a pointer to the current MdnsAvahi instance + // and this is all comes from MdnsAvahi::Init that does not have any clue about the `type` that *will* be published. + // The code needs to be updated to support that callback properly. + if (CHIP_NO_ERROR == error) + { + callback(context, type.c_str(), CHIP_NO_ERROR); + } + else + { + callback(context, nullptr, error); + } + + return error; +} + +CHIP_ERROR MdnsAvahi::StopPublish() +{ + CHIP_ERROR error = CHIP_NO_ERROR; + mPublishedServices.clear(); + if (mGroup) + { + VerifyOrExit(avahi_entry_group_reset(mGroup) == 0, error = CHIP_ERROR_INTERNAL); + } +exit: + return error; +} + +CHIP_ERROR MdnsAvahi::Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context) +{ + AvahiServiceBrowser * browser; + BrowseContext * browseContext = chip::Platform::New(); + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + + browseContext->mInstance = this; + browseContext->mContext = context; + browseContext->mCallback = callback; + browseContext->mAddressType = addressType; + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + + browser = avahi_service_browser_new(mClient, avahiInterface, AVAHI_PROTO_UNSPEC, GetFullType(type, protocol).c_str(), nullptr, + static_cast(0), HandleBrowse, browseContext); + // Otherwise the browser will be freed in the callback + if (browser == nullptr) + { + chip::Platform::Delete(browseContext); + } + + return browser == nullptr ? CHIP_ERROR_INTERNAL : CHIP_NO_ERROR; +} + +DnssdServiceProtocol GetProtocolInType(const char * type) +{ + const char * deliminator = strrchr(type, '.'); + + if (deliminator == nullptr) + { + ChipLogError(Discovery, "Failed to find protocol in type: %s", type); + return DnssdServiceProtocol::kDnssdProtocolUnknown; + } + + if (strcmp("._tcp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolTcp; + } + if (strcmp("._udp", deliminator) == 0) + { + return DnssdServiceProtocol::kDnssdProtocolUdp; + } + + ChipLogError(Discovery, "Unknown protocol in type: %s", type); + return DnssdServiceProtocol::kDnssdProtocolUnknown; +} + +/// Copies the type from a string containing both type and protocol +/// +/// e.g. if input is "foo.bar", output is "foo", input is 'a.b._tcp", output is "a.b" +template +void CopyTypeWithoutProtocol(char (&dest)[N], const char * typeAndProtocol) +{ + const char * dotPos = strrchr(typeAndProtocol, '.'); + size_t lengthWithoutProtocol = (dotPos != nullptr) ? static_cast(dotPos - typeAndProtocol) : N; + + Platform::CopyString(dest, typeAndProtocol); + + /// above copied everything including the protocol. Truncate the protocol away. + if (lengthWithoutProtocol < N) + { + dest[lengthWithoutProtocol] = 0; + } +} + +void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags /*flags*/, + void * userdata) +{ + BrowseContext * context = static_cast(userdata); + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + context->mCallback(context->mContext, nullptr, 0, CHIP_ERROR_INTERNAL); + avahi_service_browser_free(browser); + chip::Platform::Delete(context); + break; + case AVAHI_BROWSER_NEW: + ChipLogProgress(DeviceLayer, "Avahi browse: cache new"); + if (strcmp("local", domain) == 0) + { + DnssdService service = {}; + + Platform::CopyString(service.mName, name); + CopyTypeWithoutProtocol(service.mType, type); + service.mProtocol = GetProtocolInType(type); + service.mAddressType = context->mAddressType; + service.mTransportType = ToAddressType(protocol); + service.mInterface = Inet::InterfaceId::Null(); + if (interface != AVAHI_IF_UNSPEC) + { + service.mInterface = static_cast(interface); + } + service.mType[kDnssdTypeMaxSize] = 0; + context->mServices.push_back(service); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + ChipLogProgress(DeviceLayer, "Avahi browse: all for now"); + context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), CHIP_NO_ERROR); + avahi_service_browser_free(browser); + chip::Platform::Delete(context); + break; + case AVAHI_BROWSER_REMOVE: + ChipLogProgress(DeviceLayer, "Avahi browse: remove"); + if (strcmp("local", domain) == 0) + { + std::remove_if(context->mServices.begin(), context->mServices.end(), [name, type](const DnssdService & service) { + return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol); + }); + } + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + ChipLogProgress(DeviceLayer, "Avahi browse: cache exhausted"); + break; + } +} + +CHIP_ERROR MdnsAvahi::Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, Inet::IPAddressType addressType, + Inet::IPAddressType transportType, Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) +{ + AvahiServiceResolver * resolver; + AvahiIfIndex avahiInterface = static_cast(interface.GetPlatformInterface()); + ResolveContext * resolveContext = chip::Platform::New(); + CHIP_ERROR error = CHIP_NO_ERROR; + + resolveContext->mInstance = this; + resolveContext->mCallback = callback; + resolveContext->mContext = context; + + if (!interface.IsPresent()) + { + avahiInterface = AVAHI_IF_UNSPEC; + } + + Platform::CopyString(resolveContext->mName, name); + resolveContext->mInterface = avahiInterface; + resolveContext->mTransport = ToAvahiProtocol(transportType); + resolveContext->mAddressType = ToAvahiProtocol(addressType); + resolveContext->mFullType = GetFullType(type, protocol); + + resolver = avahi_service_resolver_new(mClient, avahiInterface, resolveContext->mTransport, name, + resolveContext->mFullType.c_str(), nullptr, resolveContext->mAddressType, + static_cast(0), HandleResolve, resolveContext); + // Otherwise the resolver will be freed in the callback + if (resolver == nullptr) + { + error = CHIP_ERROR_INTERNAL; + chip::Platform::Delete(resolveContext); + } + + return error; +} + +void MdnsAvahi::HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * /*domain*/, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata) +{ + ResolveContext * context = reinterpret_cast(userdata); + std::vector textEntries; + + switch (event) + { + case AVAHI_RESOLVER_FAILURE: + if (context->mAttempts++ < 3) + { + ChipLogProgress(DeviceLayer, "Re-trying resolve"); + avahi_service_resolver_free(resolver); + resolver = avahi_service_resolver_new(context->mInstance->mClient, context->mInterface, context->mTransport, + context->mName, context->mFullType.c_str(), nullptr, context->mAddressType, + static_cast(0), HandleResolve, context); + if (resolver == nullptr) + { + ChipLogError(DeviceLayer, "Avahi resolve failed on retry"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + chip::Platform::Delete(context); + } + return; + } + ChipLogError(DeviceLayer, "Avahi resolve failed"); + context->mCallback(context->mContext, nullptr, Span(), CHIP_ERROR_INTERNAL); + break; + case AVAHI_RESOLVER_FOUND: + DnssdService result = {}; + + result.mAddress.SetValue(chip::Inet::IPAddress()); + ChipLogError(DeviceLayer, "Avahi resolve found"); + + Platform::CopyString(result.mName, name); + CopyTypeWithoutProtocol(result.mType, type); + result.mProtocol = GetProtocolInType(type); + result.mPort = port; + result.mAddressType = ToAddressType(protocol); + result.mInterface = Inet::InterfaceId::Null(); + // It's not clear if we can get the actual value from avahi, so just assume default. + result.mTtlSeconds = AVAHI_DEFAULT_TTL_HOST_NAME; + if (interface != AVAHI_IF_UNSPEC) + { + result.mInterface = static_cast(interface); + } + Platform::CopyString(result.mHostName, host_name); + // Returned value is full QName, want only host part. + char * dot = strchr(result.mHostName, '.'); + if (dot != nullptr) + { + *dot = '\0'; + } + + CHIP_ERROR result_err = CHIP_ERROR_INVALID_ADDRESS; + if (address) + { + switch (address->proto) + { + case AVAHI_PROTO_INET: +#if INET_CONFIG_ENABLE_IPV4 + struct in_addr addr4; + + memcpy(&addr4, &(address->data.ipv4), sizeof(addr4)); + result.mAddress.SetValue(chip::Inet::IPAddress(addr4)); + result_err = CHIP_NO_ERROR; +#else + ChipLogError(Discovery, "Ignoring IPv4 mDNS address."); +#endif + break; + case AVAHI_PROTO_INET6: + struct in6_addr addr6; + + memcpy(&addr6, &(address->data.ipv6), sizeof(addr6)); + result.mAddress.SetValue(chip::Inet::IPAddress(addr6)); + result_err = CHIP_NO_ERROR; + break; + default: + break; + } + } + + while (txt != nullptr) + { + for (size_t i = 0; i < txt->size; i++) + { + if (txt->text[i] == '=') + { + txt->text[i] = '\0'; + textEntries.push_back(TextEntry{ reinterpret_cast(txt->text), &txt->text[i + 1], txt->size - i - 1 }); + break; + } + } + txt = txt->next; + } + + if (!textEntries.empty()) + { + result.mTextEntries = textEntries.data(); + } + result.mTextEntrySize = textEntries.size(); + + if (result_err == CHIP_NO_ERROR) + { + context->mCallback(context->mContext, &result, Span(), CHIP_NO_ERROR); + } + else + { + context->mCallback(context->mContext, nullptr, Span(), result_err); + } + break; + } + + avahi_service_resolver_free(resolver); + chip::Platform::Delete(context); +} + +CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + return MdnsAvahi::GetInstance().Init(initCallback, errorCallback, context); +} + +CHIP_ERROR ChipDnssdShutdown() +{ + return MdnsAvahi::GetInstance().Shutdown(); +} + +CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context) +{ + VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + if (strcmp(service->mHostName, "") != 0) + { + ReturnErrorOnFailure(MdnsAvahi::GetInstance().SetHostname(service->mHostName)); + } + + return MdnsAvahi::GetInstance().PublishService(*service, callback, context); +} + +CHIP_ERROR ChipDnssdRemoveServices() +{ + return MdnsAvahi::GetInstance().StopPublish(); +} + +CHIP_ERROR ChipDnssdFinalizeServiceUpdate() +{ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context) +{ + return MdnsAvahi::GetInstance().Browse(type, protocol, addressType, interface, callback, context); +} + +CHIP_ERROR ChipDnssdResolve(DnssdService * browseResult, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context) + +{ + VerifyOrReturnError(browseResult != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + return MdnsAvahi::GetInstance().Resolve(browseResult->mName, browseResult->mType, browseResult->mProtocol, + browseResult->mAddressType, Inet::IPAddressType::kAny, interface, callback, context); +} + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/webos/DnssdImpl.h b/src/platform/webos/DnssdImpl.h new file mode 100644 index 00000000000000..ffc6d47a2b5d3c --- /dev/null +++ b/src/platform/webos/DnssdImpl.h @@ -0,0 +1,173 @@ +/* + * + * Copyright (c) 2020 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 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "lib/dnssd/platform/Dnssd.h" + +struct AvahiWatch +{ + int mSocket; + chip::System::SocketWatchToken mSocketWatch; + AvahiWatchCallback mCallback; ///< The function to be called when interested events happened on mFd. + AvahiWatchEvent mPendingIO; ///< The pending events from the currently active or most recent callback. + void * mContext; ///< A pointer to application-specific context. + void * mPoller; ///< The poller created this watch. +}; + +struct AvahiTimeout +{ + std::chrono::steady_clock::time_point mAbsTimeout; ///< Absolute time when this timer timeout. + AvahiTimeoutCallback mCallback; ///< The function to be called when timeout. + bool mEnabled; ///< Whether the timeout is enabled. + void * mContext; ///< The pointer to application-specific context. + void * mPoller; ///< The poller created this timer. +}; + +namespace chip { +namespace Dnssd { + +class Poller +{ +public: + Poller(void); + + void HandleTimeout(); + + const AvahiPoll * GetAvahiPoll(void) const { return &mAvahiPoller; } + +private: + static AvahiWatch * WatchNew(const struct AvahiPoll * poller, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, + void * context); + AvahiWatch * WatchNew(int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void * context); + + static void WatchUpdate(AvahiWatch * watch, AvahiWatchEvent event); + + static AvahiWatchEvent WatchGetEvents(AvahiWatch * watch); + + static void WatchFree(AvahiWatch * watch); + void WatchFree(AvahiWatch & watch); + + static AvahiTimeout * TimeoutNew(const AvahiPoll * poller, const struct timeval * timeout, AvahiTimeoutCallback callback, + void * context); + AvahiTimeout * TimeoutNew(const struct timeval * timeout, AvahiTimeoutCallback callback, void * context); + + static void TimeoutUpdate(AvahiTimeout * timer, const struct timeval * timeout); + + static void TimeoutFree(AvahiTimeout * timer); + void TimeoutFree(AvahiTimeout & timer); + + void SystemTimerUpdate(AvahiTimeout * timer); + static void SystemTimerCallback(System::Layer * layer, void * data); + + std::vector> mWatches; + std::vector> mTimers; + std::chrono::steady_clock::time_point mEarliestTimeout; + + AvahiPoll mAvahiPoller; +}; + +class MdnsAvahi +{ +public: + MdnsAvahi(const MdnsAvahi &) = delete; + MdnsAvahi & operator=(const MdnsAvahi &) = delete; + + CHIP_ERROR Init(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context); + CHIP_ERROR Shutdown(); + CHIP_ERROR SetHostname(const char * hostname); + CHIP_ERROR PublishService(const DnssdService & service, DnssdPublishCallback callback, void * context); + CHIP_ERROR StopPublish(); + CHIP_ERROR Browse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context); + CHIP_ERROR Resolve(const char * name, const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, + chip::Inet::IPAddressType transportType, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, + void * context); + + Poller & GetPoller() { return mPoller; } + + static MdnsAvahi & GetInstance() { return sInstance; } + +private: + struct BrowseContext + { + MdnsAvahi * mInstance; + DnssdBrowseCallback mCallback; + void * mContext; + Inet::IPAddressType mAddressType; + std::vector mServices; + }; + + struct ResolveContext + { + MdnsAvahi * mInstance; + DnssdResolveCallback mCallback; + void * mContext; + char mName[Common::kInstanceNameMaxLength + 1]; + AvahiIfIndex mInterface; + AvahiProtocol mTransport; + AvahiProtocol mAddressType; + std::string mFullType; + uint8_t mAttempts = 0; + }; + + MdnsAvahi() : mClient(nullptr), mGroup(nullptr) {} + static MdnsAvahi sInstance; + + static void HandleClientState(AvahiClient * client, AvahiClientState state, void * context); + void HandleClientState(AvahiClient * client, AvahiClientState state); + + static void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state, void * context); + void HandleGroupState(AvahiEntryGroup * group, AvahiEntryGroupState state); + + static void HandleBrowse(AvahiServiceBrowser * broswer, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, + const char * name, const char * type, const char * domain, AvahiLookupResultFlags flags, + void * userdata); + static void HandleResolve(AvahiServiceResolver * resolver, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char * name, const char * type, const char * domain, + const char * host_name, const AvahiAddress * address, uint16_t port, AvahiStringList * txt, + AvahiLookupResultFlags flags, void * userdata); + + DnssdAsyncReturnCallback mInitCallback; + DnssdAsyncReturnCallback mErrorCallback; + void * mAsyncReturnContext; + + std::set mPublishedServices; + AvahiClient * mClient; + AvahiEntryGroup * mGroup; + Poller mPoller; +}; + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/webos/GlibTypeDeleter.h b/src/platform/webos/GlibTypeDeleter.h new file mode 100644 index 00000000000000..476a00a9ae5c50 --- /dev/null +++ b/src/platform/webos/GlibTypeDeleter.h @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2020 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 + +template +class UniquePointerReceiver +{ +public: + UniquePointerReceiver(std::unique_ptr & target) : mTarget(target) {} + + ~UniquePointerReceiver() { mTarget.reset(mValue); } + + T *& Get() { return mValue; } + +private: + std::unique_ptr & mTarget; + T * mValue = nullptr; +}; + +template +UniquePointerReceiver MakeUniquePointerReceiver(std::unique_ptr & target) +{ + return UniquePointerReceiver(target); +} + +struct GFree +{ + void operator()(gpointer object) { g_free(object); } +}; + +struct GObjectDeleter +{ + void operator()(gpointer object) { g_object_unref(object); } +}; + +struct GErrorDeleter +{ + void operator()(GError * object) { g_error_free(object); } +}; + +struct GVariantDeleter +{ + void operator()(GVariant * object) { g_variant_unref(object); } +}; + +struct GVariantIterDeleter +{ + void operator()(GVariantIter * object) { g_variant_iter_free(object); } +}; + +struct GBytesDeleter +{ + void operator()(GBytes * object) { g_bytes_unref(object); } +}; diff --git a/src/platform/webos/KeyValueStoreManagerImpl.cpp b/src/platform/webos/KeyValueStoreManagerImpl.cpp new file mode 100644 index 00000000000000..259d86174bc130 --- /dev/null +++ b/src/platform/webos/KeyValueStoreManagerImpl.cpp @@ -0,0 +1,114 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Platform-specific implementatiuon of KVS for linux. + */ + +#include + +#include +#include + +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; + +CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size, + size_t offset_bytes) +{ + size_t read_size; + + // Copy data into value buffer + VerifyOrReturnError(value != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + // On linux read first without a buffer which returns the size, and then + // use a local buffer to read the entire object, which allows partial and + // offset reads. + CHIP_ERROR err = mStorage.ReadValueBin(key, nullptr, 0, read_size); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + if ((err != CHIP_NO_ERROR) && (err != CHIP_ERROR_BUFFER_TOO_SMALL)) + { + return err; + } + if (offset_bytes > read_size) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + Platform::ScopedMemoryBuffer buf; + VerifyOrReturnError(buf.Alloc(read_size), CHIP_ERROR_NO_MEMORY); + ReturnErrorOnFailure(mStorage.ReadValueBin(key, buf.Get(), read_size, read_size)); + + size_t copy_size = std::min(value_size, read_size - offset_bytes); + if (read_bytes_size != nullptr) + { + *read_bytes_size = copy_size; + } + ::memcpy(value, buf.Get() + offset_bytes, copy_size); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, size_t value_size) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + err = mStorage.WriteValueBin(key, reinterpret_cast(value), value_size); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + err = mStorage.ClearValue(key); + + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = mStorage.Commit(); + SuccessOrExit(err); + +exit: + return err; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/KeyValueStoreManagerImpl.h b/src/platform/webos/KeyValueStoreManagerImpl.h new file mode 100644 index 00000000000000..9b02e95941ec22 --- /dev/null +++ b/src/platform/webos/KeyValueStoreManagerImpl.h @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Platform-specific implementation of KVS for linux. + */ + +#pragma once + +#include + +namespace chip { +namespace DeviceLayer { +namespace PersistedStorage { + +class KeyValueStoreManagerImpl : public KeyValueStoreManager +{ +public: + /** + * @brief + * Initalize the KVS, must be called before using. + */ + CHIP_ERROR Init(const char * file) { return mStorage.Init(file); } + + CHIP_ERROR _Get(const char * key, void * value, size_t value_size, size_t * read_bytes_size = nullptr, size_t offset = 0); + CHIP_ERROR _Delete(const char * key); + CHIP_ERROR _Put(const char * key, const void * value, size_t value_size); + +private: + DeviceLayer::Internal::ChipLinuxStorage mStorage; + + // ===== Members for internal use by the following friends. + friend KeyValueStoreManager & KeyValueStoreMgr(); + friend KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(); + + static KeyValueStoreManagerImpl sInstance; +}; + +/** + * Returns the public interface of the KeyValueStoreManager singleton object. + * + * Chip applications should use this to access features of the KeyValueStoreManager object + * that are common to all platforms. + */ +inline KeyValueStoreManager & KeyValueStoreMgr(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the KeyValueStoreManager singleton object. + * + * Chip applications can use this to gain access to features of the KeyValueStoreManager + * that are specific to the ESP32 platform. + */ +inline KeyValueStoreManagerImpl & KeyValueStoreMgrImpl(void) +{ + return KeyValueStoreManagerImpl::sInstance; +} + +} // namespace PersistedStorage +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/Logging.cpp b/src/platform/webos/Logging.cpp index 194db48a1c02e7..08929b94f88a65 100644 --- a/src/platform/webos/Logging.cpp +++ b/src/platform/webos/Logging.cpp @@ -1,19 +1,4 @@ -/* - * - * Copyright (c) 2022 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. - */ +/* See Project CHIP LICENSE file for licensing information. */ #include #include @@ -25,6 +10,10 @@ #include #include +#ifdef USE_SYSLOG +#include +#endif + namespace chip { namespace DeviceLayer { @@ -52,11 +41,15 @@ void ENFORCE_FORMAT(3, 0) LogV(const char * module, uint8_t category, const char // indicate the error occurred during getting time. gettimeofday(&tv, nullptr); +#ifdef USE_SYSLOG + vsyslog(category, msg, v); +#else printf("[%" PRIu64 ".%06" PRIu64 "][%lld:%lld] CHIP:%s: ", static_cast(tv.tv_sec), static_cast(tv.tv_usec), static_cast(syscall(SYS_getpid)), static_cast(syscall(SYS_gettid)), module); vprintf(msg, v); printf("\n"); fflush(stdout); +#endif // Let the application know that a log message has been emitted. DeviceLayer::OnLogOutput(); diff --git a/src/platform/webos/MainLoop.cpp b/src/platform/webos/MainLoop.cpp new file mode 100644 index 00000000000000..52f4a95f5f2956 --- /dev/null +++ b/src/platform/webos/MainLoop.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2022 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 "MainLoop.h" + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +gboolean MainLoop::ThreadTimeout(gpointer userData) +{ + LoopData * loopData = reinterpret_cast(userData); + + VerifyOrReturnError(loopData != NULL, G_SOURCE_REMOVE); + + if (loopData->mTimeoutFn) + { + ChipLogProgress(DeviceLayer, "[Timeout] thread timeout function"); + loopData->mTimeoutFn(loopData->mTimeoutUserData); + } + + if (loopData->mMainLoop) + { + ChipLogProgress(DeviceLayer, "[Timeout] main loop %p", loopData->mMainLoop); + g_main_loop_quit(loopData->mMainLoop); + } + + return G_SOURCE_REMOVE; +} + +void MainLoop::SetThreadTimeout(LoopData * loopData, guint interval) +{ + VerifyOrReturn(loopData != NULL); + + GSource * source = g_timeout_source_new_seconds(interval); + g_source_set_callback(source, ThreadTimeout, reinterpret_cast(loopData), NULL); + g_source_attach(source, loopData->mMainContext); + g_source_unref(source); +} + +gpointer MainLoop::ThreadMainHandler(gpointer data) +{ + LoopData * loopData = reinterpret_cast(data); + + g_main_context_push_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[PUSH] main context %p", loopData->mMainContext); + + ChipLogProgress(DeviceLayer, "[RUN] main loop %p", loopData->mMainLoop); + g_main_loop_run(loopData->mMainLoop); + ChipLogProgress(DeviceLayer, "[QUIT] main loop %p", loopData->mMainLoop); + g_main_loop_unref(loopData->mMainLoop); + loopData->mMainLoop = NULL; + + g_main_context_pop_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[POP] main context %p", loopData->mMainContext); + g_main_context_unref(loopData->mMainContext); + loopData->mMainContext = NULL; + + return NULL; +} + +gpointer MainLoop::ThreadAsyncHandler(gpointer data) +{ + LoopData * loopData = reinterpret_cast(data); + + g_main_context_push_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[PUSH] async context %p", loopData->mMainContext); + + ChipLogProgress(DeviceLayer, "[RUN] async loop %p", loopData->mMainLoop); + g_main_loop_run(loopData->mMainLoop); + ChipLogProgress(DeviceLayer, "[QUIT] async loop %p", loopData->mMainLoop); + g_main_loop_unref(loopData->mMainLoop); + loopData->mMainLoop = NULL; + + g_main_context_pop_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[POP] async context %p", loopData->mMainContext); + g_main_context_unref(loopData->mMainContext); + loopData->mMainContext = NULL; + + chip::Platform::Delete(loopData); + + return NULL; +} + +bool MainLoop::Init(initFn_t initFn, gpointer userData) +{ + bool result; + LoopData * loopData = chip::Platform::New(); + + VerifyOrReturnError(loopData != NULL, false); + + loopData->mMainContext = g_main_context_new(); + loopData->mMainLoop = g_main_loop_new(loopData->mMainContext, FALSE); + + g_main_context_push_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[PUSH] main context %p", loopData->mMainContext); + + result = initFn(userData); + + g_main_context_pop_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[POP] main context %p", loopData->mMainContext); + + if (result != false) + { + loopData->mThread = g_thread_new("ThreadMainHandler", ThreadMainHandler, reinterpret_cast(loopData)); + mLoopData.push_back(loopData); + } + + return result; +} + +void MainLoop::DeleteData(LoopData * loopData) +{ + if (loopData->mMainLoop) + { + g_main_loop_quit(loopData->mMainLoop); + g_thread_join(loopData->mThread); + } + + chip::Platform::Delete(loopData); +} + +void MainLoop::Deinit(void) +{ + std::vector::const_iterator iter = mLoopData.cbegin(); + while (iter != mLoopData.cend()) + { + DeleteData(*iter); + mLoopData.erase(iter); + iter++; + } +} + +bool MainLoop::AsyncRequest(asyncFn_t asyncFn, gpointer asyncUserData, guint interval, timeoutFn_t timeoutFn, + gpointer timeoutUserData) +{ + bool result = false; + LoopData * loopData = chip::Platform::New(); + + VerifyOrReturnError(loopData != NULL, false); + + loopData->mMainContext = g_main_context_new(); + loopData->mMainLoop = g_main_loop_new(loopData->mMainContext, FALSE); + + g_main_context_push_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[PUSH] async context %p", loopData->mMainContext); + + result = asyncFn(loopData->mMainLoop, asyncUserData); + + g_main_context_pop_thread_default(loopData->mMainContext); + ChipLogProgress(DeviceLayer, "[POP] async context %p", loopData->mMainContext); + + if (result != false) + { + loopData->mThread = g_thread_new("ThreadAsyncHandler", ThreadAsyncHandler, reinterpret_cast(loopData)); + + if (interval) + { + loopData->mTimeoutFn = timeoutFn; + loopData->mTimeoutUserData = timeoutUserData; + SetThreadTimeout(loopData, interval); + } + } + + return result; +} + +gpointer MainLoop::ThreadStartLSMainLoopHandler(gpointer data) +{ + LoopData * loopData = reinterpret_cast(data); + + ChipLogProgress(DeviceLayer, "[RUN] LS2 loop %p", loopData->mMainLoop); + g_main_loop_run(loopData->mMainLoop); + ChipLogProgress(DeviceLayer, "[QUIT] LS2 loop %p", loopData->mMainLoop); + g_main_loop_unref(loopData->mMainLoop); + loopData->mMainLoop = NULL; + + g_main_context_unref(loopData->mMainContext); + loopData->mMainContext = NULL; + + chip::Platform::Delete(loopData); + + return NULL; +} + +bool MainLoop::StartLSMainLoop(void) +{ + bool result = true; + LSError lserror; + LSErrorInit(&lserror); + + LoopData * loopData = chip::Platform::New(); + + VerifyOrReturnError(loopData != NULL, false); + + loopData->mMainContext = g_main_context_new(); + loopData->mMainLoop = g_main_loop_new(loopData->mMainContext, FALSE); + + if (!LSRegister("com.webos.service.matter-1234", &mLSHandle, &lserror)) + { + g_print("Unable to register to luna-bus\n"); + LSErrorFree(&lserror); + result = false; + } + + if (!LSGmainAttach(mLSHandle, loopData->mMainLoop, &lserror)) + { + g_print("Unable to attach service\n"); + result = false; + } + + if (result != false) + { + loopData->mThread = + g_thread_new("ThreadStartLSMainLoopHandler", ThreadStartLSMainLoopHandler, reinterpret_cast(loopData)); + } + + return result; +} + +MainLoop & MainLoop::Instance(void) +{ + static MainLoop sMainLoop; + return sMainLoop; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/MainLoop.h b/src/platform/webos/MainLoop.h new file mode 100644 index 00000000000000..6ae910291f7303 --- /dev/null +++ b/src/platform/webos/MainLoop.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 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 +#include + +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +typedef gboolean (*initFn_t)(gpointer userData); +typedef gboolean (*asyncFn_t)(GMainLoop * mainLoop, gpointer userData); +typedef void (*timeoutFn_t)(gpointer userData); + +class LoopData +{ +public: + LoopData() : mMainContext(NULL), mMainLoop(NULL), mThread(NULL), mTimeoutFn(NULL), mTimeoutUserData(NULL) {} + + GMainContext * mMainContext; + GMainLoop * mMainLoop; + GThread * mThread; + timeoutFn_t mTimeoutFn; + gpointer mTimeoutUserData; +}; + +class MainLoop +{ +public: + bool Init(initFn_t initFn, gpointer userData = NULL); + void Deinit(void); + bool AsyncRequest(asyncFn_t asyncFn, gpointer asyncUserData = NULL, guint interval = 0, timeoutFn_t timeoutFn = NULL, + gpointer timeoutUserData = NULL); + + bool StartLSMainLoop(void); + + static MainLoop & Instance(void); + + LSHandle * mLSHandle; + +private: + MainLoop() {} + void DeleteData(LoopData * loopData); + static gboolean ThreadTimeout(gpointer userData); + void SetThreadTimeout(LoopData * loopData, guint interval); + static gpointer ThreadMainHandler(gpointer data); + static gpointer ThreadAsyncHandler(gpointer data); + static gpointer ThreadStartLSMainLoopHandler(gpointer data); + + std::vector mLoopData; +}; + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/NetworkCommissioningDriver.h b/src/platform/webos/NetworkCommissioningDriver.h new file mode 100644 index 00000000000000..a474535cd2e3ff --- /dev/null +++ b/src/platform/webos/NetworkCommissioningDriver.h @@ -0,0 +1,161 @@ +/* + * + * 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 +#include + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +template +class LinuxScanResponseIterator : public Iterator +{ +public: + LinuxScanResponseIterator(std::vector * 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 * 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() override = 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(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + CHIP_ERROR Shutdown() override; + + // 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, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // WiFiDriver + Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) override; + void ScanNetworks(ByteSpan ssid, ScanCallback * callback) override; + +private: + bool NetworkMatch(const WiFiNetwork & network, ByteSpan networkId); + + WiFiNetworkIterator mWiFiIterator = WiFiNetworkIterator(this); + WiFiNetwork mSavedNetwork; + WiFiNetwork mStagingNetwork; + Optional mScanStatus; +}; +#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() override = default; + + private: + LinuxThreadDriver * driver; + bool exhausted = false; + }; + + // BaseDriver + NetworkIterator * GetNetworks() override { return new ThreadNetworkIterator(this); } + CHIP_ERROR Init(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) override; + CHIP_ERROR Shutdown() override; + + // 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, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; + void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + + // ThreadDriver + Status AddOrUpdateNetwork(ByteSpan operationalDataset, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; + void ScanNetworks(ThreadDriver::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/webos/NetworkCommissioningThreadDriver.cpp b/src/platform/webos/NetworkCommissioningThreadDriver.cpp new file mode 100644 index 00000000000000..776d12e6ded803 --- /dev/null +++ b/src/platform/webos/NetworkCommissioningThreadDriver.cpp @@ -0,0 +1,205 @@ +/* + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +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(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), CHIP_NO_ERROR); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(mStagingNetwork) == CHIP_NO_ERROR, CHIP_NO_ERROR); + + mSavedNetwork.Init(mStagingNetwork.AsByteSpan()); + + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxThreadDriver::Shutdown() +{ + ThreadStackMgrImpl().SetNetworkStatusChangeCallback(nullptr); + 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, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + uint8_t extpanid[kSizeExtendedPanId]; + uint8_t newExtpanid[kSizeExtendedPanId]; + Thread::OperationalDataset newDataset; + outDebugText.reduce_size(0); + outNetworkIndex = 0; + 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, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + 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, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + uint8_t extpanid[kSizeExtendedPanId]; + if (!mStagingNetwork.IsCommissioned()) + { + return Status::kNetworkNotFound; + } + 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, 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); + // The ThreadScan callback will always be invoked in CHIP mainloop, which is strictly after this function + 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; + + Thread::OperationalDataset currentDataset; + uint8_t enabledExtPanId[Thread::kSizeExtendedPanId]; + + // The Thread network is not actually enabled. + VerifyOrReturnError(ConnectivityMgrImpl().IsThreadAttached(), true); + VerifyOrReturnError(ThreadStackMgrImpl().GetThreadProvision(currentDataset) == 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/webos/NetworkCommissioningWiFiDriver.cpp b/src/platform/webos/NetworkCommissioningWiFiDriver.cpp new file mode 100644 index 00000000000000..be181f2b7e6486 --- /dev/null +++ b/src/platform/webos/NetworkCommissioningWiFiDriver.cpp @@ -0,0 +1,227 @@ +/* + * + * 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 +#include +#include +#include + +#include +#include +#include + +using namespace chip; +using namespace chip::Thread; + +namespace chip { +namespace DeviceLayer { +namespace NetworkCommissioning { + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +// TODO(#14172): 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(BaseDriver::NetworkStatusChangeCallback * networkStatusChangeCallback) +{ + 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; + + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(networkStatusChangeCallback); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LinuxWiFiDriver::Shutdown() +{ + ConnectivityMgrImpl().SetNetworkStatusChangeCallback(nullptr); + 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, MutableCharSpan & outDebugText, + uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + VerifyOrReturnError(mStagingNetwork.ssidLen == 0 || NetworkMatch(mStagingNetwork, ssid), Status::kBoundsExceeded); + + static_assert(sizeof(WiFiNetwork::ssid) <= std::numeric_limits::max(), + "Max length of WiFi ssid exceeds the limit of ssidLen field"); + static_assert(sizeof(WiFiNetwork::credentials) <= std::numeric_limits::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(credentials.size()); + + memcpy(mStagingNetwork.ssid, ssid.data(), ssid.size()); + mStagingNetwork.ssidLen = static_cast(ssid.size()); + + return Status::kSuccess; +} + +Status LinuxWiFiDriver::RemoveNetwork(ByteSpan networkId, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) +{ + outDebugText.reduce_size(0); + outNetworkIndex = 0; + 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, MutableCharSpan & outDebugText) +{ + outDebugText.reduce_size(0); + 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) + { + mScanStatus.SetValue(Status::kUnknownError); + callback->OnFinished(Status::kUnknownError, CharSpan(), nullptr); + } + else + { + // On linux platform, once "scan" is started, we can say the result will always be success. + mScanStatus.SetValue(Status::kSuccess); + } +} + +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 configuredNetwork; + CHIP_ERROR err = DeviceLayer::ConnectivityMgrImpl().GetConfiguredNetwork(configuredNetwork); + if (err == CHIP_NO_ERROR) + { + if (DeviceLayer::ConnectivityMgrImpl().IsWiFiStationConnected() && configuredNetwork.networkIDLen == item.networkIDLen && + memcmp(configuredNetwork.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/webos/PlatformManagerImpl.cpp b/src/platform/webos/PlatformManagerImpl.cpp new file mode 100644 index 00000000000000..30be3740335c4a --- /dev/null +++ b/src/platform/webos/PlatformManagerImpl.cpp @@ -0,0 +1,417 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object + * for webOS platforms. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 +#include +#define gettid() syscall(SYS_gettid) +#endif + +using namespace ::chip::app::Clusters; + +namespace chip { +namespace DeviceLayer { + +PlatformManagerImpl PlatformManagerImpl::sInstance; + +namespace { + +void SignalHandler(int signum) +{ + ChipLogDetail(DeviceLayer, "Caught signal %d", signum); + + switch (signum) + { + case SIGUSR1: + PlatformMgrImpl().HandleSoftwareFault(SoftwareDiagnostics::Events::SoftwareFault::Id); + break; + case SIGUSR2: + PlatformMgrImpl().HandleGeneralFault(GeneralDiagnostics::Events::HardwareFaultChange::Id); + break; + case SIGHUP: + PlatformMgrImpl().HandleGeneralFault(GeneralDiagnostics::Events::RadioFaultChange::Id); + break; + case SIGTTIN: + PlatformMgrImpl().HandleGeneralFault(GeneralDiagnostics::Events::NetworkFaultChange::Id); + break; + case SIGTSTP: + PlatformMgrImpl().HandleSwitchEvent(Switch::Events::SwitchLatched::Id); + break; + default: + break; + } +} + +#if CHIP_WITH_GIO +void GDBus_Thread() +{ + GMainLoop * loop = g_main_loop_new(nullptr, false); + + g_main_loop_run(loop); + g_main_loop_unref(loop); +} +#endif +} // namespace + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI +void PlatformManagerImpl::WiFIIPChangeListener() +{ + int sock; + if ((sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) + { + ChipLogError(DeviceLayer, "Failed to init netlink socket for ip addresses."); + return; + } + + struct sockaddr_nl addr; + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_IPV4_IFADDR; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) + { + ChipLogError(DeviceLayer, "Failed to bind netlink socket for ip addresses."); + return; + } + + ssize_t len; + char buffer[4096]; + for (struct nlmsghdr * header = reinterpret_cast(buffer); (len = recv(sock, header, sizeof(buffer), 0)) > 0;) + { + for (struct nlmsghdr * messageHeader = header; + (NLMSG_OK(messageHeader, static_cast(len))) && (messageHeader->nlmsg_type != NLMSG_DONE); + messageHeader = NLMSG_NEXT(messageHeader, len)) + { + if (header->nlmsg_type == RTM_NEWADDR) + { + struct ifaddrmsg * addressMessage = (struct ifaddrmsg *) NLMSG_DATA(header); + struct rtattr * routeInfo = IFA_RTA(addressMessage); + size_t rtl = IFA_PAYLOAD(header); + + for (; rtl && RTA_OK(routeInfo, rtl); routeInfo = RTA_NEXT(routeInfo, rtl)) + { + if (routeInfo->rta_type == IFA_LOCAL) + { + char name[IFNAMSIZ]; + if (if_indextoname(addressMessage->ifa_index, name) == nullptr) + { + ChipLogError(DeviceLayer, "Error %d when getting the interface name at index: %d", errno, + addressMessage->ifa_index); + continue; + } + + if (strcmp(name, ConnectivityManagerImpl::GetWiFiIfName()) != 0) + { + continue; + } + + char ipStrBuf[chip::Inet::IPAddress::kMaxStringLength] = { 0 }; + inet_ntop(AF_INET, RTA_DATA(routeInfo), ipStrBuf, sizeof(ipStrBuf)); + ChipLogDetail(DeviceLayer, "Got IP address on interface: %s IP: %s", name, ipStrBuf); + + ChipDeviceEvent event; + event.Type = DeviceEventType::kInternetConnectivityChange; + event.InternetConnectivityChange.IPv4 = kConnectivity_Established; + event.InternetConnectivityChange.IPv6 = kConnectivity_NoChange; + VerifyOrDie(chip::Inet::IPAddress::FromString(ipStrBuf, event.InternetConnectivityChange.ipAddress)); + + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogDetail(DeviceLayer, "Failed to report IP address: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + } + } + } + } +} +#endif // #if CHIP_DEVICE_CONFIG_ENABLE_WIFI + +CHIP_ERROR PlatformManagerImpl::_InitChipStack() +{ + struct sigaction action; + + memset(&action, 0, sizeof(action)); + action.sa_handler = SignalHandler; + sigaction(SIGHUP, &action, nullptr); + sigaction(SIGTTIN, &action, nullptr); + sigaction(SIGUSR1, &action, nullptr); + sigaction(SIGUSR2, &action, nullptr); + sigaction(SIGTSTP, &action, nullptr); + +#if CHIP_WITH_GIO + GError * error = nullptr; + + this->mpGDBusConnection = UniqueGDBusConnection(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error)); + + std::thread gdbusThread(GDBus_Thread); + gdbusThread.detach(); +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_WIFI + std::thread wifiIPThread(WiFIIPChangeListener); + wifiIPThread.detach(); +#endif + + // Initialize the configuration system. + ReturnErrorOnFailure(Internal::PosixConfig::Init()); + SetConfigurationMgr(&ConfigurationManagerImpl::GetDefaultInstance()); + SetDiagnosticDataProvider(&DiagnosticDataProviderImpl::GetDefaultInstance()); + SetDeviceInfoProvider(&DeviceInfoProviderImpl::GetDefaultInstance()); + + // Call _InitChipStack() on the generic implementation base class + // to finish the initialization process. + ReturnErrorOnFailure(Internal::GenericPlatformManagerImpl_POSIX::_InitChipStack()); + + mStartTime = System::SystemClock().GetMonotonicTimestamp(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PlatformManagerImpl::_Shutdown() +{ + uint64_t upTime = 0; + + if (GetDiagnosticDataProvider().GetUpTime(upTime) == CHIP_NO_ERROR) + { + uint32_t totalOperationalHours = 0; + + if (ConfigurationMgr().GetTotalOperationalHours(totalOperationalHours) == CHIP_NO_ERROR) + { + ConfigurationMgr().StoreTotalOperationalHours(totalOperationalHours + static_cast(upTime / 3600)); + } + else + { + ChipLogError(DeviceLayer, "Failed to get total operational hours of the Node"); + } + } + else + { + ChipLogError(DeviceLayer, "Failed to get current uptime since the Node’s last reboot"); + } + + return Internal::GenericPlatformManagerImpl_POSIX::_Shutdown(); +} + +void PlatformManagerImpl::HandleGeneralFault(uint32_t EventId) +{ + GeneralDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetGeneralDiagnosticsDelegate(); + + if (delegate == nullptr) + { + ChipLogError(DeviceLayer, "No delegate registered to handle General Diagnostics event"); + return; + } + + if (EventId == GeneralDiagnostics::Events::HardwareFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following hardware faults statically. + ReturnOnFailure(previous.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_RADIO)); + ReturnOnFailure(previous.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_POWER_SOURCE)); + + ReturnOnFailure(current.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_RADIO)); + ReturnOnFailure(current.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_SENSOR)); + ReturnOnFailure(current.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_POWER_SOURCE)); + ReturnOnFailure(current.add(EMBER_ZCL_HARDWARE_FAULT_TYPE_USER_INTERFACE_FAULT)); +#endif + delegate->OnHardwareFaultsDetected(previous, current); + } + else if (EventId == GeneralDiagnostics::Events::RadioFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnOnFailure(previous.add(EMBER_ZCL_RADIO_FAULT_TYPE_WI_FI_FAULT)); + ReturnOnFailure(previous.add(EMBER_ZCL_RADIO_FAULT_TYPE_THREAD_FAULT)); + + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_TYPE_WI_FI_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_TYPE_CELLULAR_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_TYPE_THREAD_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_TYPE_NFC_FAULT)); +#endif + delegate->OnRadioFaultsDetected(previous, current); + } + else if (EventId == GeneralDiagnostics::Events::NetworkFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + +#if CHIP_CONFIG_TEST + // On Linux Simulation, set following radio faults statically. + ReturnOnFailure(previous.add(EMBER_ZCL_NETWORK_FAULT_TYPE_HARDWARE_FAILURE)); + ReturnOnFailure(previous.add(EMBER_ZCL_NETWORK_FAULT_TYPE_NETWORK_JAMMED)); + + ReturnOnFailure(current.add(EMBER_ZCL_NETWORK_FAULT_TYPE_HARDWARE_FAILURE)); + ReturnOnFailure(current.add(EMBER_ZCL_NETWORK_FAULT_TYPE_NETWORK_JAMMED)); + ReturnOnFailure(current.add(EMBER_ZCL_NETWORK_FAULT_TYPE_CONNECTION_FAILED)); +#endif + delegate->OnNetworkFaultsDetected(previous, current); + } + else + { + ChipLogError(DeviceLayer, "Unknow event ID:%d", EventId); + } +} + +void PlatformManagerImpl::HandleSoftwareFault(uint32_t EventId) +{ + SoftwareDiagnosticsDelegate * delegate = GetDiagnosticDataProvider().GetSoftwareDiagnosticsDelegate(); + + if (delegate != nullptr) + { + SoftwareDiagnostics::Structs::SoftwareFaultStruct::Type softwareFault; + char threadName[kMaxThreadNameLength + 1]; + + softwareFault.id = gettid(); + strncpy(threadName, std::to_string(softwareFault.id).c_str(), kMaxThreadNameLength); + threadName[kMaxThreadNameLength] = '\0'; + softwareFault.name = CharSpan::fromCharString(threadName); + softwareFault.faultRecording = ByteSpan(Uint8::from_const_char("FaultRecording"), strlen("FaultRecording")); + + delegate->OnSoftwareFaultDetected(softwareFault); + } +} + +void PlatformManagerImpl::HandleSwitchEvent(uint32_t EventId) +{ + SwitchDeviceControlDelegate * delegate = DeviceControlServer::DeviceControlSvr().GetSwitchDelegate(); + + if (delegate == nullptr) + { + ChipLogError(DeviceLayer, "No delegate registered to handle Switch event"); + return; + } + + if (EventId == Switch::Events::SwitchLatched::Id) + { + uint8_t newPosition = 0; + +#if CHIP_CONFIG_TEST + newPosition = 100; +#endif + delegate->OnSwitchLatched(newPosition); + } + else if (EventId == Switch::Events::InitialPress::Id) + { + uint8_t newPosition = 0; + +#if CHIP_CONFIG_TEST + newPosition = 100; +#endif + delegate->OnInitialPressed(newPosition); + } + else if (EventId == Switch::Events::LongPress::Id) + { + uint8_t newPosition = 0; + +#if CHIP_CONFIG_TEST + newPosition = 100; +#endif + delegate->OnLongPressed(newPosition); + } + else if (EventId == Switch::Events::ShortRelease::Id) + { + uint8_t previousPosition = 0; + +#if CHIP_CONFIG_TEST + previousPosition = 50; +#endif + delegate->OnShortReleased(previousPosition); + } + else if (EventId == Switch::Events::LongRelease::Id) + { + uint8_t previousPosition = 0; + +#if CHIP_CONFIG_TEST + previousPosition = 50; +#endif + delegate->OnLongReleased(previousPosition); + } + else if (EventId == Switch::Events::MultiPressOngoing::Id) + { + uint8_t newPosition = 0; + uint8_t currentNumberOfPressesCounted = 0; + +#if CHIP_CONFIG_TEST + newPosition = 10; + currentNumberOfPressesCounted = 5; +#endif + delegate->OnMultiPressOngoing(newPosition, currentNumberOfPressesCounted); + } + else if (EventId == Switch::Events::MultiPressComplete::Id) + { + uint8_t newPosition = 0; + uint8_t totalNumberOfPressesCounted = 0; + +#if CHIP_CONFIG_TEST + newPosition = 10; + totalNumberOfPressesCounted = 5; +#endif + delegate->OnMultiPressComplete(newPosition, totalNumberOfPressesCounted); + } + else + { + ChipLogError(DeviceLayer, "Unknow event ID:%d", EventId); + } +} + +#if CHIP_WITH_GIO +GDBusConnection * PlatformManagerImpl::GetGDBusConnection() +{ + return this->mpGDBusConnection.get(); +} +#endif + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/PlatformManagerImpl.h b/src/platform/webos/PlatformManagerImpl.h new file mode 100644 index 00000000000000..46846c615cefb9 --- /dev/null +++ b/src/platform/webos/PlatformManagerImpl.h @@ -0,0 +1,116 @@ +/* + * + * Copyright (c) 2020-2021 Project CHIP Authors + * Copyright (c) 2018 Nest Labs, Inc. + * + * 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. + */ + +/** + * @file + * Provides an implementation of the PlatformManager object. + */ + +#pragma once + +#include +#include + +#if CHIP_WITH_GIO +#include +#endif + +namespace chip { +namespace DeviceLayer { + +/** + * Concrete implementation of the PlatformManager singleton object for webOS platforms. + */ +class PlatformManagerImpl final : public PlatformManager, public Internal::GenericPlatformManagerImpl_POSIX +{ + // Allow the PlatformManager interface class to delegate method calls to + // the implementation methods provided by this class. + friend PlatformManager; + + // Allow the generic implementation base class to call helper methods on + // this class. +#ifndef DOXYGEN_SHOULD_SKIP_THIS + friend Internal::GenericPlatformManagerImpl_POSIX; +#endif + +public: + // ===== Platform-specific members that may be accessed directly by the application. +#if CHIP_WITH_GIO + GDBusConnection * GetGDBusConnection(); +#endif + + System::Clock::Timestamp GetStartTime() { return mStartTime; } + + void HandleGeneralFault(uint32_t EventId); + void HandleSoftwareFault(uint32_t EventId); + void HandleSwitchEvent(uint32_t EventId); + +private: + // ===== Methods that implement the PlatformManager abstract interface. + + CHIP_ERROR _InitChipStack(); + CHIP_ERROR _Shutdown(); + + // ===== Members for internal use by the following friends. + + friend PlatformManager & PlatformMgr(); + friend PlatformManagerImpl & PlatformMgrImpl(); + friend class Internal::BLEManagerImpl; + + System::Clock::Timestamp mStartTime = System::Clock::kZero; + + static PlatformManagerImpl sInstance; + + // 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. + static void WiFIIPChangeListener(); + +#if CHIP_WITH_GIO + struct GDBusConnectionDeleter + { + void operator()(GDBusConnection * conn) { g_object_unref(conn); } + }; + using UniqueGDBusConnection = std::unique_ptr; + UniqueGDBusConnection mpGDBusConnection; +#endif +}; + +/** + * Returns the public interface of the PlatformManager singleton object. + * + * chip applications should use this to access features of the PlatformManager object + * that are common to all platforms. + */ +inline PlatformManager & PlatformMgr() +{ + return PlatformManagerImpl::sInstance; +} + +/** + * Returns the platform-specific implementation of the PlatformManager singleton object. + * + * chip applications can use this to gain access to features of the PlatformManager + * that are specific to the platform. + */ +inline PlatformManagerImpl & PlatformMgrImpl() +{ + return PlatformManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/PosixConfig.cpp b/src/platform/webos/PosixConfig.cpp new file mode 100644 index 00000000000000..4f43a092358550 --- /dev/null +++ b/src/platform/webos/PosixConfig.cpp @@ -0,0 +1,591 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019-2020 Google LLC. + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Utilities for interacting with multiple file partitions and maps + * key-value config calls to the correct partition. + */ + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +static ChipLinuxStorage gChipLinuxFactoryStorage; +static ChipLinuxStorage gChipLinuxConfigStorage; +static ChipLinuxStorage gChipLinuxCountersStorage; + +// *** CAUTION ***: Changing the names or namespaces of these values will *break* existing devices. + +// NVS namespaces used to store device configuration information. +const char PosixConfig::kConfigNamespace_ChipFactory[] = "chip-factory"; +const char PosixConfig::kConfigNamespace_ChipConfig[] = "chip-config"; +const char PosixConfig::kConfigNamespace_ChipCounters[] = "chip-counters"; + +// Keys stored in the Chip-factory namespace +const PosixConfig::Key PosixConfig::kConfigKey_SerialNum = { kConfigNamespace_ChipFactory, "serial-num" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceId = { kConfigNamespace_ChipFactory, "device-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceCert = { kConfigNamespace_ChipFactory, "device-cert" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDeviceICACerts = { kConfigNamespace_ChipFactory, "device-ca-certs" }; +const PosixConfig::Key PosixConfig::kConfigKey_MfrDevicePrivateKey = { kConfigNamespace_ChipFactory, "device-key" }; +const PosixConfig::Key PosixConfig::kConfigKey_HardwareVersion = { kConfigNamespace_ChipFactory, "hardware-ver" }; +const PosixConfig::Key PosixConfig::kConfigKey_ManufacturingDate = { kConfigNamespace_ChipFactory, "mfg-date" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupPinCode = { kConfigNamespace_ChipFactory, "pin-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_SetupDiscriminator = { kConfigNamespace_ChipFactory, "discriminator" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pIterationCount = { kConfigNamespace_ChipFactory, "iteration-count" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pSalt = { kConfigNamespace_ChipFactory, "salt" }; +const PosixConfig::Key PosixConfig::kConfigKey_Spake2pVerifier = { kConfigNamespace_ChipFactory, "verifier" }; +const PosixConfig::Key PosixConfig::kConfigKey_VendorId = { kConfigNamespace_ChipFactory, "vendor-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ProductId = { kConfigNamespace_ChipFactory, "product-id" }; + +// Keys stored in the Chip-config namespace +const PosixConfig::Key PosixConfig::kConfigKey_FabricId = { kConfigNamespace_ChipConfig, "fabric-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ServiceConfig = { kConfigNamespace_ChipConfig, "service-config" }; +const PosixConfig::Key PosixConfig::kConfigKey_PairedAccountId = { kConfigNamespace_ChipConfig, "account-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_ServiceId = { kConfigNamespace_ChipConfig, "service-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_FabricSecret = { kConfigNamespace_ChipConfig, "fabric-secret" }; +const PosixConfig::Key PosixConfig::kConfigKey_GroupKeyIndex = { kConfigNamespace_ChipConfig, "group-key-index" }; +const PosixConfig::Key PosixConfig::kConfigKey_LastUsedEpochKeyId = { kConfigNamespace_ChipConfig, "last-ek-id" }; +const PosixConfig::Key PosixConfig::kConfigKey_FailSafeArmed = { kConfigNamespace_ChipConfig, "fail-safe-armed" }; +const PosixConfig::Key PosixConfig::kConfigKey_WiFiStationSecType = { kConfigNamespace_ChipConfig, "sta-sec-type" }; +const PosixConfig::Key PosixConfig::kConfigKey_RegulatoryLocation = { kConfigNamespace_ChipConfig, "regulatory-location" }; +const PosixConfig::Key PosixConfig::kConfigKey_CountryCode = { kConfigNamespace_ChipConfig, "country-code" }; +const PosixConfig::Key PosixConfig::kConfigKey_Breadcrumb = { kConfigNamespace_ChipConfig, "breadcrumb" }; +const PosixConfig::Key PosixConfig::kConfigKey_LocationCapability = { kConfigNamespace_ChipConfig, "location-capability" }; +const PosixConfig::Key PosixConfig::kConfigKey_UniqueId = { kConfigNamespace_ChipFactory, "unique-id" }; + +// Keys stored in the Chip-counters namespace +const PosixConfig::Key PosixConfig::kCounterKey_RebootCount = { kConfigNamespace_ChipCounters, "reboot-count" }; +const PosixConfig::Key PosixConfig::kCounterKey_UpTime = { kConfigNamespace_ChipCounters, "up-time" }; +const PosixConfig::Key PosixConfig::kCounterKey_TotalOperationalHours = { kConfigNamespace_ChipCounters, + "total-operational-hours" }; +const PosixConfig::Key PosixConfig::kCounterKey_BootReason = { kConfigNamespace_ChipCounters, "boot-reason" }; + +// Prefix used for NVS keys that contain Chip group encryption keys. +const char PosixConfig::kGroupKeyNamePrefix[] = "gk-"; + +ChipLinuxStorage * PosixConfig::GetStorageForNamespace(Key key) +{ + if (strcmp(key.Namespace, kConfigNamespace_ChipFactory) == 0) + return &gChipLinuxFactoryStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipConfig) == 0) + return &gChipLinuxConfigStorage; + + if (strcmp(key.Namespace, kConfigNamespace_ChipCounters) == 0) + return &gChipLinuxCountersStorage; + + return nullptr; +} + +CHIP_ERROR PosixConfig::Init() +{ + return PersistedStorage::KeyValueStoreMgrImpl().Init(CHIP_CONFIG_KVS_PATH); +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, bool & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + uint32_t intVal; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, intVal); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + val = (intVal != 0); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint16_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint32_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValue(Key key, uint64_t & val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + // Special case the MfrDeviceId value, optionally allowing it to be read as a blob containing + // a 64-bit big-endian integer, instead of a u64 value. + if (key == kConfigKey_MfrDeviceId) + { + uint8_t deviceIdBytes[sizeof(uint64_t)]; + size_t deviceIdLen = sizeof(deviceIdBytes); + size_t deviceIdOutLen; + err = storage->ReadValueBin(key.Name, deviceIdBytes, deviceIdLen, deviceIdOutLen); + if (err == CHIP_NO_ERROR) + { + VerifyOrExit(deviceIdOutLen == sizeof(deviceIdBytes), err = CHIP_ERROR_INCORRECT_STATE); + val = Encoding::BigEndian::Get64(deviceIdBytes); + ExitNow(); + } + } + + err = storage->ReadValue(key.Name, val); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueStr(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ReadValueBin(key.Name, buf, bufSize, outLen); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + outLen = 0; + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + else if (err == CHIP_ERROR_BUFFER_TOO_SMALL) + { + err = (buf == nullptr) ? CHIP_NO_ERROR : CHIP_ERROR_BUFFER_TOO_SMALL; + } + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, bool val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %s", key.Namespace, key.Name, val ? "true" : "false"); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint16_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %u (0x%X)", key.Namespace, key.Name, val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint32_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu32 " (0x%" PRIX32 ")", key.Namespace, key.Name, val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValue(Key key, uint64_t val) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValue(key.Name, val); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = %" PRIu64 " (0x%" PRIX64 ")", key.Namespace, key.Name, val, val); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (str != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueStr(key.Name, str); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = \"%s\"", key.Namespace, key.Name, str); + } + + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ +#if CHIP_CONFIG_MEMORY_MGMT_MALLOC + CHIP_ERROR err; + char * strCopy = nullptr; + + if (str != nullptr) + { + strCopy = strndup(str, strLen); + VerifyOrExit(strCopy != nullptr, err = CHIP_ERROR_NO_MEMORY); + } + + err = PosixConfig::WriteConfigValueStr(key, strCopy); + +exit: + if (strCopy != nullptr) + { + free(strCopy); + } + return err; +#else +#error "Unsupported CHIP_CONFIG_MEMORY_MGMT configuration" +#endif +} + +CHIP_ERROR PosixConfig::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + if (data != nullptr) + { + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->WriteValueBin(key.Name, data, dataLen); + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS set: %s/%s = (blob length %u)", key.Namespace, key.Name, + static_cast(dataLen)); + } + else + { + err = ClearConfigValue(key); + SuccessOrExit(err); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearConfigValue(Key key) +{ + CHIP_ERROR err; + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearValue(key.Name); + if (err == CHIP_ERROR_KEY_NOT_FOUND) + { + ExitNow(err = CHIP_NO_ERROR); + } + SuccessOrExit(err); + + // Commit the value to the persistent store. + err = storage->Commit(); + SuccessOrExit(err); + + ChipLogProgress(DeviceLayer, "NVS erase: %s/%s", key.Namespace, key.Name); + +exit: + return err; +} + +bool PosixConfig::ConfigValueExists(Key key) +{ + ChipLinuxStorage * storage; + + storage = GetStorageForNamespace(key); + if (storage == nullptr) + return false; + + return storage->HasValue(key.Name); +} + +CHIP_ERROR PosixConfig::EnsureNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipFactory) == 0) + { + storage = &gChipLinuxFactoryStorage; + err = storage->Init(CHIP_DEFAULT_FACTORY_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + err = storage->Init(CHIP_DEFAULT_CONFIG_PATH); + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + err = storage->Init(CHIP_DEFAULT_DATA_PATH); + } + + SuccessOrExit(err); + +exit: + return err; +} + +CHIP_ERROR PosixConfig::ClearNamespace(const char * ns) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage = nullptr; + + if (strcmp(ns, kConfigNamespace_ChipConfig) == 0) + { + storage = &gChipLinuxConfigStorage; + } + else if (strcmp(ns, kConfigNamespace_ChipCounters) == 0) + { + storage = &gChipLinuxCountersStorage; + } + + VerifyOrExit(storage != nullptr, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetConfig() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset configuration"); + + storage = &gChipLinuxConfigStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +CHIP_ERROR PosixConfig::FactoryResetCounters() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ChipLinuxStorage * storage; + + ChipLogProgress(DeviceLayer, "Performing factory reset counters"); + + storage = &gChipLinuxCountersStorage; + if (storage == nullptr) + { + ChipLogError(DeviceLayer, "Storage get failed"); + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + } + SuccessOrExit(err); + + err = storage->ClearAll(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage ClearAll failed: %s", ErrorStr(err)); + } + SuccessOrExit(err); + + err = storage->Commit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Storage Commit failed: %s", ErrorStr(err)); + } + +exit: + return err; +} + +void PosixConfig::RunConfigUnitTest() +{ + // Run common unit test. + ::chip::DeviceLayer::Internal::RunConfigUnitTest(); +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/PosixConfig.h b/src/platform/webos/PosixConfig.h new file mode 100644 index 00000000000000..44823124667f3b --- /dev/null +++ b/src/platform/webos/PosixConfig.h @@ -0,0 +1,138 @@ +/* + * + * Copyright (c) 2020-2022 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. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * webOS platforms. + */ + +#pragma once + +#include +#include + +#include + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +class ChipLinuxStorage; + +/** + * Provides functions and definitions for accessing device configuration information on the Posix. + * + * This class is designed to be mixed-in to concrete implementation classes as a means to + * provide access to configuration information to generic base classes. + */ +class PosixConfig +{ +public: + struct Key; + + // Maximum length of an NVS key name. + static constexpr size_t kMaxConfigKeyNameLength = 15; + + // NVS namespaces used to store device configuration information. + static const char kConfigNamespace_ChipFactory[]; + static const char kConfigNamespace_ChipConfig[]; + static const char kConfigNamespace_ChipCounters[]; + + // Key definitions for well-known keys. + static const Key kConfigKey_SerialNum; + static const Key kConfigKey_UniqueId; + static const Key kConfigKey_MfrDeviceId; + static const Key kConfigKey_MfrDeviceCert; + static const Key kConfigKey_MfrDeviceICACerts; + static const Key kConfigKey_MfrDevicePrivateKey; + static const Key kConfigKey_HardwareVersion; + static const Key kConfigKey_ManufacturingDate; + static const Key kConfigKey_SetupPinCode; + static const Key kConfigKey_FabricId; + static const Key kConfigKey_ServiceConfig; + static const Key kConfigKey_PairedAccountId; + static const Key kConfigKey_ServiceId; + static const Key kConfigKey_FabricSecret; + static const Key kConfigKey_GroupKeyIndex; + static const Key kConfigKey_LastUsedEpochKeyId; + static const Key kConfigKey_FailSafeArmed; + static const Key kConfigKey_WiFiStationSecType; + static const Key kConfigKey_SetupDiscriminator; + static const Key kConfigKey_RegulatoryLocation; + static const Key kConfigKey_CountryCode; + static const Key kConfigKey_Breadcrumb; + static const Key kConfigKey_LocationCapability; + static const Key kConfigKey_Spake2pIterationCount; + static const Key kConfigKey_Spake2pSalt; + static const Key kConfigKey_Spake2pVerifier; + static const Key kConfigKey_VendorId; + static const Key kConfigKey_ProductId; + + static const Key kCounterKey_RebootCount; + static const Key kCounterKey_UpTime; + static const Key kCounterKey_TotalOperationalHours; + static const Key kCounterKey_BootReason; + + static const char kGroupKeyNamePrefix[]; + + static CHIP_ERROR Init(); + + // Config value accessors. + static CHIP_ERROR ReadConfigValue(Key key, bool & val); + static CHIP_ERROR ReadConfigValue(Key key, uint16_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint32_t & val); + static CHIP_ERROR ReadConfigValue(Key key, uint64_t & val); + static CHIP_ERROR ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen); + static CHIP_ERROR WriteConfigValue(Key key, bool val); + static CHIP_ERROR WriteConfigValue(Key key, uint16_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint32_t val); + static CHIP_ERROR WriteConfigValue(Key key, uint64_t val); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str); + static CHIP_ERROR WriteConfigValueStr(Key key, const char * str, size_t strLen); + static CHIP_ERROR WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen); + static CHIP_ERROR ClearConfigValue(Key key); + static bool ConfigValueExists(Key key); + static CHIP_ERROR FactoryResetConfig(); + static CHIP_ERROR FactoryResetCounters(); + static void RunConfigUnitTest(); + + // NVS Namespace helper functions. + static CHIP_ERROR EnsureNamespace(const char * ns); + static CHIP_ERROR ClearNamespace(const char * ns); + +private: + static ChipLinuxStorage * GetStorageForNamespace(Key key); +}; + +struct PosixConfig::Key +{ + const char * Namespace; + const char * Name; + + bool operator==(const Key & other) const; +}; + +inline bool PosixConfig::Key::operator==(const Key & other) const +{ + return strcmp(Namespace, other.Namespace) == 0 && strcmp(Name, other.Name) == 0; +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/SystemTimeSupport.cpp b/src/platform/webos/SystemTimeSupport.cpp new file mode 100644 index 00000000000000..a4b5ec0b75229a --- /dev/null +++ b/src/platform/webos/SystemTimeSupport.cpp @@ -0,0 +1,70 @@ +/* + * + * Copyright (c) 2018 Nest Labs, Inc. + * All rights reserved. + * + * 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. + */ + +/** + * @file + * Provides implementations of the CHIP System Layer platform + * time/clock functions that are suitable for use on the Posix platform. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +namespace chip { +namespace System { +namespace Clock { + +namespace Internal { +ClockImpl gClockImpl; +} // namespace Internal + +Microseconds64 ClockImpl::GetMonotonicMicroseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +Milliseconds64 ClockImpl::GetMonotonicMilliseconds64() +{ + return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); +} + +CHIP_ERROR ClockImpl::GetClock_RealTime(Clock::Microseconds64 & aCurTime) +{ + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR ClockImpl::GetClock_RealTimeMS(Clock::Milliseconds64 & aCurTime) +{ + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR ClockImpl::SetClock_RealTime(Clock::Microseconds64 aNewCurTime) +{ + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +} // namespace Clock +} // namespace System +} // namespace chip diff --git a/src/platform/webos/ThreadStackManagerImpl.cpp b/src/platform/webos/ThreadStackManagerImpl.cpp new file mode 100644 index 00000000000000..07f326615d8269 --- /dev/null +++ b/src/platform/webos/ThreadStackManagerImpl.cpp @@ -0,0 +1,758 @@ +/* + * + * Copyright (c) 2020 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 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::chip::app; +using namespace ::chip::app::Clusters; +using namespace chip::DeviceLayer::NetworkCommissioning; + +namespace chip { +namespace DeviceLayer { + +ThreadStackManagerImpl ThreadStackManagerImpl::sInstance; + +constexpr char ThreadStackManagerImpl::kDBusOpenThreadService[]; +constexpr char ThreadStackManagerImpl::kDBusOpenThreadObjectPath[]; + +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDisabled[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleDetached[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleChild[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleRouter[]; +constexpr char ThreadStackManagerImpl::kOpenthreadDeviceRoleLeader[]; + +constexpr char ThreadStackManagerImpl::kPropertyDeviceRole[]; + +ThreadStackManagerImpl::ThreadStackManagerImpl() : mAttached(false) {} + +CHIP_ERROR ThreadStackManagerImpl::_InitThreadStack() +{ + std::unique_ptr err; + mProxy.reset(openthread_io_openthread_border_router_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, + kDBusOpenThreadService, kDBusOpenThreadObjectPath, + nullptr, &MakeUniquePointerReceiver(err).Get())); + if (!mProxy) + { + ChipLogError(DeviceLayer, "openthread: failed to create openthread dbus proxy %s", err ? err->message : "unknown error"); + return CHIP_ERROR_INTERNAL; + } + + g_signal_connect(mProxy.get(), "g-properties-changed", G_CALLBACK(OnDbusPropertiesChanged), this); + + // If get property is called inside dbus thread (we are going to make it so), XXX_get_XXX can be used instead of XXX_dup_XXX + // which is a little bit faster and the returned object doesn't need to be freed. Same for all following get properties. + std::unique_ptr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (role) + { + ThreadDevcieRoleChangedHandler(role.get()); + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data) +{ + ThreadStackManagerImpl * me = reinterpret_cast(user_data); + if (g_variant_n_children(changed_properties) > 0) + { + const gchar * key; + GVariant * value; + + std::unique_ptr iter; + g_variant_get(changed_properties, "a{sv}", &MakeUniquePointerReceiver(iter).Get()); + if (!iter) + return; + while (g_variant_iter_loop(iter.get(), "{&sv}", &key, &value)) + { + if (key == nullptr || value == nullptr) + continue; + // ownership of key and value is still holding by the iter + DeviceLayer::SystemLayer().ScheduleLambda([me]() { me->_UpdateNetworkStatus(); }); + + if (strcmp(key, kPropertyDeviceRole) == 0) + { + const gchar * value_str = g_variant_get_string(value, nullptr); + if (value_str == nullptr) + continue; + ChipLogProgress(DeviceLayer, "Thread role changed to: %s", value_str); + me->ThreadDevcieRoleChangedHandler(value_str); + } + } + } +} + +void ThreadStackManagerImpl::ThreadDevcieRoleChangedHandler(const gchar * role) +{ + bool attached = strcmp(role, kOpenthreadDeviceRoleDetached) != 0 && strcmp(role, kOpenthreadDeviceRoleDisabled) != 0; + + ChipDeviceEvent event = ChipDeviceEvent{}; + + if (attached != mAttached) + { + event.Type = DeviceEventType::kThreadConnectivityChange; + event.ThreadConnectivityChange.Result = + attached ? ConnectivityChange::kConnectivity_Established : ConnectivityChange::kConnectivity_Lost; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread connectivity change: %" CHIP_ERROR_FORMAT, status.Format()); + } + } + mAttached = attached; + + event.Type = DeviceEventType::kThreadStateChange; + event.ThreadStateChange.RoleChanged = true; + CHIP_ERROR status = PlatformMgr().PostEvent(&event); + if (status != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to post thread state change: %" CHIP_ERROR_FORMAT, status.Format()); + } +} + +void ThreadStackManagerImpl::_ProcessThreadActivity() {} + +bool ThreadStackManagerImpl::_HaveRouteToAddress(const Inet::IPAddress & destAddr) +{ + if (!mProxy || !_IsThreadAttached()) + { + return false; + } + if (destAddr.IsIPv6LinkLocal()) + { + return true; + } + + std::unique_ptr routes(openthread_io_openthread_border_router_dup_external_routes(mProxy.get())); + if (!routes) + return false; + + if (g_variant_n_children(routes.get()) > 0) + { + std::unique_ptr iter; + g_variant_get(routes.get(), "av", &MakeUniquePointerReceiver(iter).Get()); + if (!iter) + return false; + + GVariant * route; + while (g_variant_iter_loop(iter.get(), "&v", &route)) + { + if (route == nullptr) + continue; + std::unique_ptr prefix; + guint16 rloc16; + guchar preference; + gboolean stable; + gboolean nextHopIsThisDevice; + g_variant_get(route, "(&vqybb)", &MakeUniquePointerReceiver(prefix).Get(), &rloc16, &preference, &stable, + &nextHopIsThisDevice); + if (!prefix) + continue; + + std::unique_ptr address; + guchar prefixLength; + g_variant_get(prefix.get(), "(&vy)", &MakeUniquePointerReceiver(address).Get(), &prefixLength); + if (!address) + continue; + + GBytes * bytes = g_variant_get_data_as_bytes(address.get()); // the ownership still hold by address + if (bytes == nullptr) + continue; + gsize size; + gconstpointer data = g_bytes_get_data(bytes, &size); + if (data == nullptr) + continue; + if (size != sizeof(struct in6_addr)) + continue; + + Inet::IPPrefix p; + p.IPAddr = Inet::IPAddress(*reinterpret_cast(data)); + p.Length = prefixLength; + + if (p.MatchAddress(destAddr)) + { + return true; + } + } + } + + return false; +} + +void ThreadStackManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) +{ + (void) event; + // The otbr-agent processes the Thread state handling by itself so there + // isn't much to do in the Chip stack. +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadProvision(ByteSpan netInfo) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(Thread::OperationalDataset::IsValid(netInfo), CHIP_ERROR_INVALID_ARGUMENT); + + { + std::unique_ptr bytes(g_bytes_new(netInfo.data(), netInfo.size())); + if (!bytes) + return CHIP_ERROR_NO_MEMORY; + std::unique_ptr value( + g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes.release(), true)); + if (!value) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_active_dataset_tlvs(mProxy.get(), value.release()); + } + + // post an event alerting other subsystems about change in provisioning state + ChipDeviceEvent event; + event.Type = DeviceEventType::kServiceProvisioningChange; + event.ServiceProvisioningChange.IsServiceProvisioned = true; + return PlatformMgr().PostEvent(&event); +} + +CHIP_ERROR ThreadStackManagerImpl::_GetThreadProvision(Thread::OperationalDataset & dataset) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + + { + std::unique_ptr err; + + std::unique_ptr response( + g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "ActiveDatasetTlvs"), G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, &MakeUniquePointerReceiver(err).Get())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read ActiveDatasetTlvs property: %s", err->message); + return CHIP_ERROR_INTERNAL; + } + + // Note: The actual value is wrapped by a GVariant container, wrapped in another GVariant with tuple type. + + if (response == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::unique_ptr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + std::unique_ptr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return CHIP_ERROR_KEY_NOT_FOUND; + } + + gsize size; + const uint8_t * data = reinterpret_cast(g_variant_get_fixed_array(value.get(), &size, sizeof(guchar))); + ReturnErrorOnFailure(mDataset.Init(ByteSpan(data, size))); + } + + dataset.Init(mDataset.AsByteSpan()); + + return CHIP_NO_ERROR; +} + +bool ThreadStackManagerImpl::_IsThreadProvisioned() +{ + return static_cast(mDataset).IsCommissioned(); +} + +void ThreadStackManagerImpl::_ErasePersistentInfo() +{ + static_cast(mDataset).Clear(); +} + +bool ThreadStackManagerImpl::_IsThreadEnabled() +{ + VerifyOrReturnError(mProxy, false); + + std::unique_ptr err; + + std::unique_ptr response( + g_dbus_proxy_call_sync(G_DBUS_PROXY(mProxy.get()), "org.freedesktop.DBus.Properties.Get", + g_variant_new("(ss)", "io.openthread.BorderRouter", "DeviceRole"), G_DBUS_CALL_FLAGS_NONE, -1, + nullptr, &MakeUniquePointerReceiver(err).Get())); + + if (err) + { + ChipLogError(DeviceLayer, "openthread: failed to read DeviceRole property: %s", err->message); + return false; + } + + if (response == nullptr) + { + return false; + } + + std::unique_ptr tupleContent(g_variant_get_child_value(response.get(), 0)); + + if (tupleContent == nullptr) + { + return false; + } + + std::unique_ptr value(g_variant_get_variant(tupleContent.get())); + + if (value == nullptr) + { + return false; + } + + const gchar * role = g_variant_get_string(value.get(), nullptr); + + if (role == nullptr) + { + return false; + } + + return (strcmp(role, kOpenthreadDeviceRoleDisabled) != 0); +} + +bool ThreadStackManagerImpl::_IsThreadAttached() const +{ + return mAttached; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadEnabled(bool val) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (val) + { + openthread_io_openthread_border_router_call_attach(mProxy.get(), nullptr, _OnThreadBrAttachFinished, this); + } + else + { + std::unique_ptr err; + gboolean result = + openthread_io_openthread_border_router_call_reset_sync(mProxy.get(), nullptr, &MakeUniquePointerReceiver(err).Get()); + if (err) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", err->message); + return CHIP_ERROR_INTERNAL; + } + + if (!result) + { + ChipLogError(DeviceLayer, "openthread: _SetThreadEnabled calling %s failed: %s", "Reset", "return false"); + return CHIP_ERROR_INTERNAL; + } + } + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) +{ + ThreadStackManagerImpl * this_ = reinterpret_cast(user_data); + std::unique_ptr attachRes; + std::unique_ptr 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; + if (!mProxy) + { + ChipLogError(DeviceLayer, "Cannot get device role with Thread api client: %s", ""); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + + std::unique_ptr role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); + if (!role) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + if (strcmp(role.get(), kOpenthreadDeviceRoleDetached) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleDisabled) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleChild) == 0) + { + std::unique_ptr linkMode(openthread_io_openthread_border_router_dup_link_mode(mProxy.get())); + if (!linkMode) + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; + gboolean rx_on_when_idle; + gboolean device_type; + gboolean network_data; + g_variant_get(linkMode.get(), "(bbb)", &rx_on_when_idle, &device_type, &network_data); + if (!rx_on_when_idle) + { + type = ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice; + } + else + { + type = device_type ? ConnectivityManager::ThreadDeviceType::kThreadDeviceType_FullEndDevice + : ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice; + } + return type; + } + if (strcmp(role.get(), kOpenthreadDeviceRoleLeader) == 0 || strcmp(role.get(), kOpenthreadDeviceRoleRouter) == 0) + { + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_Router; + } + + ChipLogError(DeviceLayer, "Unknown Thread role: %s", role.get()); + return ConnectivityManager::ThreadDeviceType::kThreadDeviceType_NotSupported; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType) +{ + gboolean rx_on_when_idle = true; + gboolean device_type = true; + gboolean network_data = true; + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_MinimalEndDevice) + { + network_data = false; + } + else if (deviceType == ConnectivityManager::ThreadDeviceType::kThreadDeviceType_SleepyEndDevice) + { + rx_on_when_idle = false; + network_data = false; + } + + if (!network_data) + { + std::unique_ptr linkMode(g_variant_new("(bbb)", rx_on_when_idle, device_type, network_data)); + if (!linkMode) + return CHIP_ERROR_NO_MEMORY; + openthread_io_openthread_border_router_set_link_mode(mProxy.get(), linkMode.release()); + } + + return CHIP_NO_ERROR; +} + +#if CHIP_DEVICE_CONFIG_ENABLE_SED +CHIP_ERROR ThreadStackManagerImpl::_GetSEDPollingConfig(ConnectivityManager::SEDPollingConfig & pollingConfig) +{ + (void) pollingConfig; + + ChipLogError(DeviceLayer, "Polling config is not supported on linux"); + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_SetSEDPollingConfig(const ConnectivityManager::SEDPollingConfig & pollingConfig) +{ + (void) pollingConfig; + + ChipLogError(DeviceLayer, "Polling config is not supported on linux"); + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_RequestSEDFastPollingMode(bool onOff) +{ + (void) onOff; + + ChipLogError(DeviceLayer, "Polling config is not supported on linux"); + return CHIP_ERROR_NOT_IMPLEMENTED; +} +#endif + +bool ThreadStackManagerImpl::_HaveMeshConnectivity() +{ + // TODO: Remove Weave legacy APIs + // For a leader with a child, the child is considered to have mesh connectivity + // and the leader is not, which is a very confusing definition. + // This API is Weave legacy and should be removed. + + ChipLogError(DeviceLayer, "HaveMeshConnectivity has confusing behavior and shouldn't be called"); + return false; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadStatsCounters() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyMinimal() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetAndLogThreadTopologyFull() +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPrimary802154MACAddress(uint8_t * buf) +{ + VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); + guint64 extAddr = openthread_io_openthread_border_router_get_extended_address(mProxy.get()); + + for (size_t i = 0; i < sizeof(extAddr); i++) + { + buf[sizeof(uint64_t) - i - 1] = (extAddr & UINT8_MAX); + extAddr >>= CHAR_BIT; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetExternalIPv6Address(chip::Inet::IPAddress & addr) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_GetPollPeriod(uint32_t & buf) +{ + // TODO: Remove Weave legacy APIs + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +CHIP_ERROR ThreadStackManagerImpl::_JoinerStart() +{ + // TODO: Remove Weave legacy APIs + 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(user_data); + this_->_OnNetworkScanFinished(res); +} + +void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res) +{ + std::unique_ptr scan_result; + std::unique_ptr 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 iter(nullptr); + mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter); + } + mpScanCallback = nullptr; + }); + } + } + + std::vector * scanResult = + new std::vector(); + + if (g_variant_n_children(scan_result.get()) > 0) + { + std::unique_ptr 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 %u LQI %u" + " Version %u", + 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(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 iter( + const_cast *>(scanResult)); + mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); + mpScanCallback = nullptr; + } + delete const_cast *>(scanResult); + }); +} + +void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {} + +CHIP_ERROR ThreadStackManagerImpl::_WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId, + app::AttributeValueEncoder & encoder) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + switch (attributeId) + { + case ThreadNetworkDiagnostics::Attributes::NeighborTableList::Id: + case ThreadNetworkDiagnostics::Attributes::RouteTableList::Id: + case ThreadNetworkDiagnostics::Attributes::SecurityPolicy::Id: + case ThreadNetworkDiagnostics::Attributes::OperationalDatasetComponents::Id: + case ThreadNetworkDiagnostics::Attributes::ActiveNetworkFaultsList::Id: { + err = encoder.EncodeEmptyList(); + break; + } + default: { + err = CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; + break; + } + } + + return err; +} + +CHIP_ERROR +ThreadStackManagerImpl::_AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback) +{ + // Reset the previously set callback since it will never be called in case incorrect dataset was supplied. + mpConnectCallback = nullptr; + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(false)); + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadProvision(dataset.AsByteSpan())); + + if (dataset.IsCommissioned()) + { + ReturnErrorOnFailure(DeviceLayer::ThreadStackMgr().SetThreadEnabled(true)); + mpConnectCallback = callback; + } + + return CHIP_NO_ERROR; +} + +void ThreadStackManagerImpl::_UpdateNetworkStatus() +{ + // Thread is not enabled, then we are not trying to connect to the network. + VerifyOrReturn(IsThreadEnabled() && mpStatusChangeCallback != nullptr); + + Thread::OperationalDataset dataset; + uint8_t extpanid[Thread::kSizeExtendedPanId]; + + // If we have not provisioned any Thread network, return the status from last network scan, + // If we have provisioned a network, we assume the ot-br-posix is activitely connecting to that network. + CHIP_ERROR err = ThreadStackMgrImpl().GetThreadProvision(dataset); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Failed to get configured network when updating network status: %s", err.AsString()); + return; + } + + // The Thread network is not enabled, but has a different extended pan id. + VerifyOrReturn(dataset.GetExtendedPanId(extpanid) == CHIP_NO_ERROR); + + // We have already connected to the network, thus return success. + if (ThreadStackMgrImpl().IsThreadAttached()) + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kSuccess, MakeOptional(ByteSpan(extpanid)), NullOptional); + } + else + { + mpStatusChangeCallback->OnNetworkingStatusChange(Status::kNetworkNotFound, MakeOptional(ByteSpan(extpanid)), NullOptional); + } +} + +ThreadStackManager & ThreadStackMgr() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +ThreadStackManagerImpl & ThreadStackMgrImpl() +{ + return chip::DeviceLayer::ThreadStackManagerImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/ThreadStackManagerImpl.h b/src/platform/webos/ThreadStackManagerImpl.h new file mode 100644 index 00000000000000..ce7bf3b0e56b52 --- /dev/null +++ b/src/platform/webos/ThreadStackManagerImpl.h @@ -0,0 +1,167 @@ +/* + * + * Copyright (c) 2020 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace DeviceLayer { + +class ThreadStackManagerImpl : public ThreadStackManager +{ +public: + ThreadStackManagerImpl(); + + void + SetNetworkStatusChangeCallback(NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * statusChangeCallback) + { + mpStatusChangeCallback = statusChangeCallback; + } + + CHIP_ERROR _InitThreadStack(); + void _ProcessThreadActivity(); + + CHIP_ERROR _StartThreadTask() { return CHIP_NO_ERROR; } // Intentionally left blank + void _LockThreadStack() {} // Intentionally left blank + bool _TryLockThreadStack() { return false; } // Intentionally left blank + void _UnlockThreadStack() {} // Intentionally left blank + + bool _HaveRouteToAddress(const Inet::IPAddress & destAddr); + + void _OnPlatformEvent(const ChipDeviceEvent * event); + + CHIP_ERROR _GetThreadProvision(Thread::OperationalDataset & dataset); + + 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(); + + bool _IsThreadEnabled(); + + bool _IsThreadAttached() const; + + CHIP_ERROR _AttachToThreadNetwork(const Thread::OperationalDataset & dataset, + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * callback); + + CHIP_ERROR _SetThreadEnabled(bool val); + + void _OnThreadAttachFinished(void); + + void _UpdateNetworkStatus(); + + static void _OnThreadBrAttachFinished(GObject * source_object, GAsyncResult * res, gpointer user_data); + + ConnectivityManager::ThreadDeviceType _GetThreadDeviceType(); + + CHIP_ERROR _SetThreadDeviceType(ConnectivityManager::ThreadDeviceType deviceType); + +#if CHIP_DEVICE_CONFIG_ENABLE_SED + CHIP_ERROR _GetSEDPollingConfig(ConnectivityManager::SEDPollingConfig & pollingConfig); + CHIP_ERROR _SetSEDPollingConfig(const ConnectivityManager::SEDPollingConfig & pollingConfig); + CHIP_ERROR _RequestSEDFastPollingMode(bool onOff); +#endif + + bool _HaveMeshConnectivity(); + + CHIP_ERROR _GetAndLogThreadStatsCounters(); + + CHIP_ERROR _GetAndLogThreadTopologyMinimal(); + + CHIP_ERROR _GetAndLogThreadTopologyFull(); + + CHIP_ERROR _GetPrimary802154MACAddress(uint8_t * buf); + + CHIP_ERROR _GetExternalIPv6Address(chip::Inet::IPAddress & addr); + + CHIP_ERROR _GetPollPeriod(uint32_t & buf); + + CHIP_ERROR _JoinerStart(); + + void _ResetThreadNetworkDiagnosticsCounts(); + + CHIP_ERROR _WriteThreadNetworkDiagnosticAttributeToTlv(AttributeId attributeId, app::AttributeValueEncoder & encoder); + + CHIP_ERROR _StartThreadScan(NetworkCommissioning::ThreadDriver::ScanCallback * callback); + + ~ThreadStackManagerImpl() = default; + + static ThreadStackManagerImpl sInstance; + +private: + static constexpr char kDBusOpenThreadService[] = "io.openthread.BorderRouter.wpan0"; + static constexpr char kDBusOpenThreadObjectPath[] = "/io/openthread/BorderRouter/wpan0"; + + static constexpr char kOpenthreadDeviceRoleDisabled[] = "disabled"; + static constexpr char kOpenthreadDeviceRoleDetached[] = "detached"; + static constexpr char kOpenthreadDeviceRoleChild[] = "child"; + static constexpr char kOpenthreadDeviceRoleRouter[] = "router"; + static constexpr char kOpenthreadDeviceRoleLeader[] = "leader"; + + 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 mProxy; + + static void OnDbusPropertiesChanged(OpenthreadIoOpenthreadBorderRouter * proxy, GVariant * changed_properties, + const gchar * const * invalidated_properties, gpointer user_data); + void ThreadDevcieRoleChangedHandler(const gchar * role); + + Thread::OperationalDataset mDataset = {}; + + NetworkCommissioning::ThreadDriver::ScanCallback * mpScanCallback; + NetworkCommissioning::Internal::WirelessDriver::ConnectCallback * mpConnectCallback; + NetworkCommissioning::Internal::BaseDriver::NetworkStatusChangeCallback * mpStatusChangeCallback = nullptr; + + bool mAttached; +}; + +inline void ThreadStackManagerImpl::_OnThreadAttachFinished(void) +{ + // stub for ThreadStackManager.h +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/webos/WirelessDefs.h b/src/platform/webos/WirelessDefs.h new file mode 100644 index 00000000000000..d01338fd03baf9 --- /dev/null +++ b/src/platform/webos/WirelessDefs.h @@ -0,0 +1,186 @@ +/* + * + * 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. + */ + +/** + * @file + * IEEE 802.11 Frame type definitions. + */ + +#pragma once + +/* Status codes (IEEE Std 802.11-2016, 9.4.1.9, Table 9-46) */ +#define WLAN_STATUS_SUCCESS 0 +#define WLAN_STATUS_UNSPECIFIED_FAILURE 1 +#define WLAN_STATUS_TDLS_WAKEUP_ALTERNATE 2 +#define WLAN_STATUS_TDLS_WAKEUP_REJECT 3 +#define WLAN_STATUS_SECURITY_DISABLED 5 +#define WLAN_STATUS_UNACCEPTABLE_LIFETIME 6 +#define WLAN_STATUS_NOT_IN_SAME_BSS 7 +#define WLAN_STATUS_CAPS_UNSUPPORTED 10 +#define WLAN_STATUS_REASSOC_NO_ASSOC 11 +#define WLAN_STATUS_ASSOC_DENIED_UNSPEC 12 +#define WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG 13 +#define WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION 14 +#define WLAN_STATUS_CHALLENGE_FAIL 15 +#define WLAN_STATUS_AUTH_TIMEOUT 16 +#define WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA 17 +#define WLAN_STATUS_ASSOC_DENIED_RATES 18 +#define WLAN_STATUS_ASSOC_DENIED_NOSHORT 19 +#define WLAN_STATUS_SPEC_MGMT_REQUIRED 22 +#define WLAN_STATUS_PWR_CAPABILITY_NOT_VALID 23 +#define WLAN_STATUS_SUPPORTED_CHANNEL_NOT_VALID 24 +#define WLAN_STATUS_ASSOC_DENIED_NO_SHORT_SLOT_TIME 25 +#define WLAN_STATUS_ASSOC_DENIED_NO_HT 27 +#define WLAN_STATUS_R0KH_UNREACHABLE 28 +#define WLAN_STATUS_ASSOC_DENIED_NO_PCO 29 +#define WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY 30 +#define WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION 31 +#define WLAN_STATUS_UNSPECIFIED_QOS_FAILURE 32 +#define WLAN_STATUS_DENIED_INSUFFICIENT_BANDWIDTH 33 +#define WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS 34 +#define WLAN_STATUS_DENIED_QOS_NOT_SUPPORTED 35 +#define WLAN_STATUS_REQUEST_DECLINED 37 +#define WLAN_STATUS_INVALID_PARAMETERS 38 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_CHANGES 39 +#define WLAN_STATUS_INVALID_IE 40 +#define WLAN_STATUS_GROUP_CIPHER_NOT_VALID 41 +#define WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID 42 +#define WLAN_STATUS_AKMP_NOT_VALID 43 +#define WLAN_STATUS_UNSUPPORTED_RSN_IE_VERSION 44 +#define WLAN_STATUS_INVALID_RSN_IE_CAPAB 45 +#define WLAN_STATUS_CIPHER_REJECTED_PER_POLICY 46 +#define WLAN_STATUS_TS_NOT_CREATED 47 +#define WLAN_STATUS_DIRECT_LINK_NOT_ALLOWED 48 +#define WLAN_STATUS_DEST_STA_NOT_PRESENT 49 +#define WLAN_STATUS_DEST_STA_NOT_QOS_STA 50 +#define WLAN_STATUS_ASSOC_DENIED_LISTEN_INT_TOO_LARGE 51 +#define WLAN_STATUS_INVALID_FT_ACTION_FRAME_COUNT 52 +#define WLAN_STATUS_INVALID_PMKID 53 +#define WLAN_STATUS_INVALID_MDIE 54 +#define WLAN_STATUS_INVALID_FTIE 55 +#define WLAN_STATUS_REQUESTED_TCLAS_NOT_SUPPORTED 56 +#define WLAN_STATUS_INSUFFICIENT_TCLAS_PROCESSING_RESOURCES 57 +#define WLAN_STATUS_TRY_ANOTHER_BSS 58 +#define WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED 59 +#define WLAN_STATUS_NO_OUTSTANDING_GAS_REQ 60 +#define WLAN_STATUS_GAS_RESP_NOT_RECEIVED 61 +#define WLAN_STATUS_STA_TIMED_OUT_WAITING_FOR_GAS_RESP 62 +#define WLAN_STATUS_GAS_RESP_LARGER_THAN_LIMIT 63 +#define WLAN_STATUS_REQ_REFUSED_HOME 64 +#define WLAN_STATUS_ADV_SRV_UNREACHABLE 65 +#define WLAN_STATUS_REQ_REFUSED_SSPN 67 +#define WLAN_STATUS_REQ_REFUSED_UNAUTH_ACCESS 68 +#define WLAN_STATUS_INVALID_RSNIE 72 +#define WLAN_STATUS_U_APSD_COEX_NOT_SUPPORTED 73 +#define WLAN_STATUS_U_APSD_COEX_MODE_NOT_SUPPORTED 74 +#define WLAN_STATUS_BAD_INTERVAL_WITH_U_APSD_COEX 75 +#define WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ 76 +#define WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED 77 +#define WLAN_STATUS_CANNOT_FIND_ALT_TBTT 78 +#define WLAN_STATUS_TRANSMISSION_FAILURE 79 +#define WLAN_STATUS_REQ_TCLAS_NOT_SUPPORTED 80 +#define WLAN_STATUS_TCLAS_RESOURCES_EXCHAUSTED 81 +#define WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION 82 +#define WLAN_STATUS_REJECT_WITH_SCHEDULE 83 +#define WLAN_STATUS_REJECT_NO_WAKEUP_SPECIFIED 84 +#define WLAN_STATUS_SUCCESS_POWER_SAVE_MODE 85 +#define WLAN_STATUS_PENDING_ADMITTING_FST_SESSION 86 +#define WLAN_STATUS_PERFORMING_FST_NOW 87 +#define WLAN_STATUS_PENDING_GAP_IN_BA_WINDOW 88 +#define WLAN_STATUS_REJECT_U_PID_SETTING 89 +#define WLAN_STATUS_REFUSED_EXTERNAL_REASON 92 +#define WLAN_STATUS_REFUSED_AP_OUT_OF_MEMORY 93 +#define WLAN_STATUS_REJECTED_EMERGENCY_SERVICE_NOT_SUPPORTED 94 +#define WLAN_STATUS_QUERY_RESP_OUTSTANDING 95 +#define WLAN_STATUS_REJECT_DSE_BAND 96 +#define WLAN_STATUS_TCLAS_PROCESSING_TERMINATED 97 +#define WLAN_STATUS_TS_SCHEDULE_CONFLICT 98 +#define WLAN_STATUS_DENIED_WITH_SUGGESTED_BAND_AND_CHANNEL 99 +#define WLAN_STATUS_MCCAOP_RESERVATION_CONFLICT 100 +#define WLAN_STATUS_MAF_LIMIT_EXCEEDED 101 +#define WLAN_STATUS_MCCA_TRACK_LIMIT_EXCEEDED 102 +#define WLAN_STATUS_DENIED_DUE_TO_SPECTRUM_MANAGEMENT 103 +#define WLAN_STATUS_ASSOC_DENIED_NO_VHT 104 +#define WLAN_STATUS_ENABLEMENT_DENIED 105 +#define WLAN_STATUS_RESTRICTION_FROM_AUTHORIZED_GDB 106 +#define WLAN_STATUS_AUTHORIZATION_DEENABLED 107 +#define WLAN_STATUS_FILS_AUTHENTICATION_FAILURE 112 +#define WLAN_STATUS_UNKNOWN_AUTHENTICATION_SERVER 113 +#define WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER 123 + +/* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */ +#define WLAN_REASON_UNSPECIFIED 1 +#define WLAN_REASON_PREV_AUTH_NOT_VALID 2 +#define WLAN_REASON_DEAUTH_LEAVING 3 +#define WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY 4 +#define WLAN_REASON_DISASSOC_AP_BUSY 5 +#define WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA 6 +#define WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA 7 +#define WLAN_REASON_DISASSOC_STA_HAS_LEFT 8 +#define WLAN_REASON_STA_REQ_ASSOC_WITHOUT_AUTH 9 +#define WLAN_REASON_PWR_CAPABILITY_NOT_VALID 10 +#define WLAN_REASON_SUPPORTED_CHANNEL_NOT_VALID 11 +#define WLAN_REASON_BSS_TRANSITION_DISASSOC 12 +#define WLAN_REASON_INVALID_IE 13 +#define WLAN_REASON_MICHAEL_MIC_FAILURE 14 +#define WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT 15 +#define WLAN_REASON_GROUP_KEY_UPDATE_TIMEOUT 16 +#define WLAN_REASON_IE_IN_4WAY_DIFFERS 17 +#define WLAN_REASON_GROUP_CIPHER_NOT_VALID 18 +#define WLAN_REASON_PAIRWISE_CIPHER_NOT_VALID 19 +#define WLAN_REASON_AKMP_NOT_VALID 20 +#define WLAN_REASON_UNSUPPORTED_RSN_IE_VERSION 21 +#define WLAN_REASON_INVALID_RSN_IE_CAPAB 22 +#define WLAN_REASON_IEEE_802_1X_AUTH_FAILED 23 +#define WLAN_REASON_CIPHER_SUITE_REJECTED 24 +#define WLAN_REASON_TDLS_TEARDOWN_UNREACHABLE 25 +#define WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED 26 +#define WLAN_REASON_SSP_REQUESTED_DISASSOC 27 +#define WLAN_REASON_NO_SSP_ROAMING_AGREEMENT 28 +#define WLAN_REASON_BAD_CIPHER_OR_AKM 29 +#define WLAN_REASON_NOT_AUTHORIZED_THIS_LOCATION 30 +#define WLAN_REASON_SERVICE_CHANGE_PRECLUDES_TS 31 +#define WLAN_REASON_UNSPECIFIED_QOS_REASON 32 +#define WLAN_REASON_NOT_ENOUGH_BANDWIDTH 33 +#define WLAN_REASON_DISASSOC_LOW_ACK 34 +#define WLAN_REASON_EXCEEDED_TXOP 35 +#define WLAN_REASON_STA_LEAVING 36 +#define WLAN_REASON_END_TS_BA_DLS 37 +#define WLAN_REASON_UNKNOWN_TS_BA 38 +#define WLAN_REASON_TIMEOUT 39 +#define WLAN_REASON_PEERKEY_MISMATCH 45 +#define WLAN_REASON_AUTHORIZED_ACCESS_LIMIT_REACHED 46 +#define WLAN_REASON_EXTERNAL_SERVICE_REQUIREMENTS 47 +#define WLAN_REASON_INVALID_FT_ACTION_FRAME_COUNT 48 +#define WLAN_REASON_INVALID_PMKID 49 +#define WLAN_REASON_INVALID_MDE 50 +#define WLAN_REASON_INVALID_FTE 51 +#define WLAN_REASON_MESH_PEERING_CANCELLED 52 +#define WLAN_REASON_MESH_MAX_PEERS 53 +#define WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION 54 +#define WLAN_REASON_MESH_CLOSE_RCVD 55 +#define WLAN_REASON_MESH_MAX_RETRIES 56 +#define WLAN_REASON_MESH_CONFIRM_TIMEOUT 57 +#define WLAN_REASON_MESH_INVALID_GTK 58 +#define WLAN_REASON_MESH_INCONSISTENT_PARAMS 59 +#define WLAN_REASON_MESH_INVALID_SECURITY_CAP 60 +#define WLAN_REASON_MESH_PATH_ERROR_NO_PROXY_INFO 61 +#define WLAN_REASON_MESH_PATH_ERROR_NO_FORWARDING_INFO 62 +#define WLAN_REASON_MESH_PATH_ERROR_DEST_UNREACHABLE 63 +#define WLAN_REASON_MAC_ADDRESS_ALREADY_EXISTS_IN_MBSS 64 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_REGULATORY_REQ 65 +#define WLAN_REASON_MESH_CHANNEL_SWITCH_UNSPECIFIED 66 diff --git a/src/platform/webos/dbus/openthread/BUILD.gn b/src/platform/webos/dbus/openthread/BUILD.gn new file mode 100644 index 00000000000000..746d934dc0c257 --- /dev/null +++ b/src/platform/webos/dbus/openthread/BUILD.gn @@ -0,0 +1,25 @@ +# Copyright (c) 2020 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. + +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/linux/gdbus_library.gni") + +gdbus_library("openthread") { + sources = [ "introspect.xml" ] + + c_namespace = "Openthread" + c_generate_object_manager = false + dbus_out_dir = "platform/webos/dbus/openthread" +} diff --git a/src/platform/webos/dbus/openthread/introspect.xml b/src/platform/webos/dbus/openthread/introspect.xml new file mode 100644 index 00000000000000..e583697e1f63ec --- /dev/null +++ b/src/platform/webos/dbus/openthread/introspect.xml @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +