| /* |
| * |
| * 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. |
| */ |
| |
| #include <platform/internal/CHIPDeviceLayerInternal.h> |
| #include <platform/internal/DeviceNetworkInfo.h> |
| |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/GLibTypeDeleter.h> |
| #include <platform/Linux/NetworkCommissioningDriver.h> |
| #include <platform/PlatformManager.h> |
| #include <platform/ThreadStackManager.h> |
| |
| #include <app-common/zap-generated/ids/Attributes.h> |
| |
| 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[]; |
| |
| namespace { |
| |
| struct SetActiveDatasetContext |
| { |
| OpenthreadIoOpenthreadBorderRouter * proxy; |
| ByteSpan netInfo; |
| }; |
| |
| CHIP_ERROR GLibMatterContextSetActiveDataset(SetActiveDatasetContext * context) |
| { |
| // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, |
| // all D-Bus signals will be delivered to the GLib global default main context. |
| VerifyOrDie(g_main_context_get_thread_default() != nullptr); |
| |
| GAutoPtr<GBytes> bytes(g_bytes_new(context->netInfo.data(), context->netInfo.size())); |
| if (!bytes) |
| return CHIP_ERROR_NO_MEMORY; |
| GAutoPtr<GVariant> 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(context->proxy, value.release()); |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace |
| |
| ThreadStackManagerImpl::ThreadStackManagerImpl() : mAttached(false) {} |
| |
| CHIP_ERROR ThreadStackManagerImpl::GLibMatterContextInitThreadStack(ThreadStackManagerImpl * self) |
| { |
| // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise, |
| // all D-Bus signals will be delivered to the GLib global default main context. |
| VerifyOrDie(g_main_context_get_thread_default() != nullptr); |
| |
| GAutoPtr<GError> err; |
| self->mProxy.reset(openthread_io_openthread_border_router_proxy_new_for_bus_sync( |
| G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, kDBusOpenThreadService, kDBusOpenThreadObjectPath, nullptr, |
| &err.GetReceiver())); |
| VerifyOrReturnError( |
| self->mProxy != nullptr, CHIP_ERROR_INTERNAL, |
| ChipLogError(DeviceLayer, "openthread: failed to create openthread dbus proxy %s", err ? err->message : "unknown error")); |
| |
| g_signal_connect(self->mProxy.get(), "g-properties-changed", G_CALLBACK(OnDbusPropertiesChanged), self); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ThreadStackManagerImpl::_InitThreadStack() |
| { |
| CHIP_ERROR err; |
| |
| err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextInitThreadStack, this); |
| VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to init dbus proxy")); |
| |
| // 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. |
| GAutoPtr<char> role(openthread_io_openthread_border_router_dup_device_role(mProxy.get())); |
| if (role) |
| { |
| ThreadDeviceRoleChangedHandler(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<ThreadStackManagerImpl *>(user_data); |
| if (g_variant_n_children(changed_properties) > 0) |
| { |
| const gchar * key; |
| GVariant * value; |
| |
| GAutoPtr<GVariantIter> iter; |
| g_variant_get(changed_properties, "a{sv}", &iter.GetReceiver()); |
| 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", StringOrNullMarker(value_str)); |
| me->ThreadDeviceRoleChangedHandler(value_str); |
| } |
| } |
| } |
| } |
| |
| void ThreadStackManagerImpl::ThreadDeviceRoleChangedHandler(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; |
| } |
| |
| GAutoPtr<GVariant> routes(openthread_io_openthread_border_router_dup_external_routes(mProxy.get())); |
| if (!routes) |
| return false; |
| |
| if (g_variant_n_children(routes.get()) > 0) |
| { |
| GAutoPtr<GVariantIter> iter; |
| g_variant_get(routes.get(), "av", &iter.GetReceiver()); |
| if (!iter) |
| return false; |
| |
| GVariant * route; |
| while (g_variant_iter_loop(iter.get(), "&v", &route)) |
| { |
| if (route == nullptr) |
| continue; |
| GAutoPtr<GVariant> prefix; |
| guint16 rloc16; |
| guchar preference; |
| gboolean stable; |
| gboolean nextHopIsThisDevice; |
| g_variant_get(route, "(&vqybb)", &prefix.GetReceiver(), &rloc16, &preference, &stable, &nextHopIsThisDevice); |
| if (!prefix) |
| continue; |
| |
| GAutoPtr<GVariant> address; |
| guchar prefixLength; |
| g_variant_get(prefix.get(), "(&vy)", &address.GetReceiver(), &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<const struct in6_addr *>(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); |
| |
| SetActiveDatasetContext context = { mProxy.get(), netInfo }; |
| CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextSetActiveDataset, &context); |
| VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to set active dataset")); |
| |
| // 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); |
| |
| { |
| GAutoPtr<GError> err; |
| GAutoPtr<GVariant> 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, &err.GetReceiver())); |
| |
| 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; |
| } |
| |
| GAutoPtr<GVariant> tupleContent(g_variant_get_child_value(response.get(), 0)); |
| |
| if (tupleContent == nullptr) |
| { |
| return CHIP_ERROR_KEY_NOT_FOUND; |
| } |
| |
| GAutoPtr<GVariant> value(g_variant_get_variant(tupleContent.get())); |
| |
| if (value == nullptr) |
| { |
| return CHIP_ERROR_KEY_NOT_FOUND; |
| } |
| |
| gsize size; |
| const uint8_t * data = reinterpret_cast<const uint8_t *>(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<Thread::OperationalDataset &>(mDataset).IsCommissioned(); |
| } |
| |
| void ThreadStackManagerImpl::_ErasePersistentInfo() |
| { |
| static_cast<Thread::OperationalDataset &>(mDataset).Clear(); |
| } |
| |
| bool ThreadStackManagerImpl::_IsThreadEnabled() |
| { |
| VerifyOrReturnError(mProxy, false); |
| |
| GAutoPtr<GError> err; |
| GAutoPtr<GVariant> 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, &err.GetReceiver())); |
| |
| if (err) |
| { |
| ChipLogError(DeviceLayer, "openthread: failed to read DeviceRole property: %s", err->message); |
| return false; |
| } |
| |
| if (response == nullptr) |
| { |
| return false; |
| } |
| |
| GAutoPtr<GVariant> tupleContent(g_variant_get_child_value(response.get(), 0)); |
| |
| if (tupleContent == nullptr) |
| { |
| return false; |
| } |
| |
| GAutoPtr<GVariant> 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::GLibMatterContextCallAttach(ThreadStackManagerImpl * self) |
| { |
| VerifyOrDie(g_main_context_get_thread_default() != nullptr); |
| openthread_io_openthread_border_router_call_attach(self->mProxy.get(), nullptr, _OnThreadBrAttachFinished, self); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ThreadStackManagerImpl::_SetThreadEnabled(bool val) |
| { |
| VerifyOrReturnError(mProxy, CHIP_ERROR_INCORRECT_STATE); |
| if (val) |
| { |
| CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallAttach, this); |
| VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to attach")); |
| } |
| else |
| { |
| GAutoPtr<GError> err; |
| gboolean result = openthread_io_openthread_border_router_call_reset_sync(mProxy.get(), nullptr, &err.GetReceiver()); |
| 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<ThreadStackManagerImpl *>(user_data); |
| GAutoPtr<GVariant> attachRes; |
| GAutoPtr<GError> err; |
| { |
| gboolean result = openthread_io_openthread_border_router_call_attach_finish(this_->mProxy.get(), res, &err.GetReceiver()); |
| 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; |
| } |
| |
| GAutoPtr<char> 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) |
| { |
| GAutoPtr<GVariant> 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) |
| { |
| GAutoPtr<GVariant> 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_CONFIG_ENABLE_ICD_SERVER |
| CHIP_ERROR ThreadStackManagerImpl::_SetPollingInterval(System::Clock::Milliseconds32 pollingInterval) |
| { |
| (void) pollingInterval; |
| ChipLogError(DeviceLayer, "Set ICD Polling on linux"); |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| #endif /* CHIP_CONFIG_ENABLE_ICD_SERVER */ |
| |
| 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::GLibMatterContextCallScan(ThreadStackManagerImpl * self) |
| { |
| VerifyOrDie(g_main_context_get_thread_default() != nullptr); |
| openthread_io_openthread_border_router_call_scan(self->mProxy.get(), nullptr, _OnNetworkScanFinished, self); |
| return CHIP_NO_ERROR; |
| } |
| |
| 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; |
| CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(GLibMatterContextCallScan, this); |
| VerifyOrReturnError(err == CHIP_NO_ERROR, err, ChipLogError(DeviceLayer, "openthread: failed to start scan")); |
| return CHIP_NO_ERROR; |
| } |
| |
| void ThreadStackManagerImpl::_OnNetworkScanFinished(GObject * source_object, GAsyncResult * res, gpointer user_data) |
| { |
| ThreadStackManagerImpl * this_ = reinterpret_cast<ThreadStackManagerImpl *>(user_data); |
| this_->_OnNetworkScanFinished(res); |
| } |
| |
| void ThreadStackManagerImpl::_OnNetworkScanFinished(GAsyncResult * res) |
| { |
| GAutoPtr<GVariant> scan_result; |
| GAutoPtr<GError> err; |
| { |
| gboolean result = openthread_io_openthread_border_router_call_scan_finish(mProxy.get(), &scan_result.GetReceiver(), res, |
| &err.GetReceiver()); |
| if (!result) |
| { |
| ChipLogError(DeviceLayer, "Failed to perform finish Thread network scan: %s", |
| err == nullptr ? "unknown error" : err->message); |
| DeviceLayer::SystemLayer().ScheduleLambda([this]() { |
| if (mpScanCallback != nullptr) |
| { |
| LinuxScanResponseIterator<ThreadScanResponse> iter(nullptr); |
| mpScanCallback->OnFinished(Status::kUnknownError, CharSpan(), &iter); |
| } |
| mpScanCallback = nullptr; |
| }); |
| } |
| } |
| |
| std::vector<NetworkCommissioning::ThreadScanResponse> * scanResult = |
| new std::vector<NetworkCommissioning::ThreadScanResponse>(); |
| |
| if (g_variant_n_children(scan_result.get()) > 0) |
| { |
| GAutoPtr<GVariantIter> iter; |
| g_variant_get(scan_result.get(), "a(tstayqqynyybb)", &iter.GetReceiver()); |
| if (!iter) |
| { |
| delete scanResult; |
| return; |
| } |
| |
| guint64 ext_address; |
| const gchar * network_name; |
| guint64 ext_panid; |
| const gchar * steering_data; |
| guint16 panid; |
| guint16 joiner_udp_port; |
| guint8 channel; |
| gint16 rssi; |
| guint8 lqi; |
| guint8 version; |
| gboolean is_native; |
| gboolean is_joinable; |
| |
| while (g_variant_iter_loop(iter.get(), "(tstayqqynyybb)", &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 (" ChipLogFormatX64 ") ExtPanId(" ChipLogFormatX64 ") RSSI %d LQI %u" |
| " Version %u", |
| network_name, ChipLogValueX64(ext_address), ChipLogValueX64(ext_panid), rssi, lqi, version); |
| NetworkCommissioning::ThreadScanResponse networkScanned; |
| networkScanned.panId = panid; |
| networkScanned.extendedPanId = ext_panid; |
| size_t networkNameLen = strlen(network_name); |
| if (networkNameLen > 16) |
| { |
| ChipLogProgress(DeviceLayer, "Network name is too long, ignore it."); |
| continue; |
| } |
| networkScanned.networkNameLen = static_cast<uint8_t>(networkNameLen); |
| memcpy(networkScanned.networkName, network_name, networkNameLen); |
| networkScanned.channel = channel; |
| networkScanned.version = version; |
| networkScanned.extendedAddress = 0; |
| if (rssi > std::numeric_limits<int8_t>::max()) |
| { |
| networkScanned.rssi = std::numeric_limits<int8_t>::max(); |
| } |
| else if (rssi < std::numeric_limits<int8_t>::min()) |
| { |
| networkScanned.rssi = std::numeric_limits<int8_t>::min(); |
| } |
| else |
| { |
| networkScanned.rssi = static_cast<int8_t>(rssi); |
| } |
| networkScanned.lqi = lqi; |
| |
| scanResult->push_back(networkScanned); |
| } |
| } |
| |
| DeviceLayer::SystemLayer().ScheduleLambda([this, scanResult]() { |
| // Note: We cannot post a event in ScheduleLambda since std::vector is not trivial copiable. This results in the use of |
| // const_cast but should be fine for almost all cases, since we actually handled the ownership of this element to this |
| // lambda. |
| if (mpScanCallback != nullptr) |
| { |
| LinuxScanResponseIterator<NetworkCommissioning::ThreadScanResponse> iter( |
| const_cast<std::vector<ThreadScanResponse> *>(scanResult)); |
| mpScanCallback->OnFinished(Status::kSuccess, CharSpan(), &iter); |
| mpScanCallback = nullptr; |
| } |
| delete const_cast<std::vector<ThreadScanResponse> *>(scanResult); |
| }); |
| } |
| |
| void ThreadStackManagerImpl::_ResetThreadNetworkDiagnosticsCounts() {} |
| |
| CHIP_ERROR |
| ThreadStackManagerImpl::_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 |