blob: f377af95065a6d3812978fc07bd6db7756fb36a0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/net/routes/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/time.h>
// clang-format off
#include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
#include <Weave/DeviceLayer/internal/DeviceNetworkInfo.h>
#include <Weave/DeviceLayer/PlatformManager.h>
#include <Weave/DeviceLayer/ThreadStackManager.h>
#include <Weave/Support/TraitEventUtils.h>
// clang-format on
#include "thread_stack_manager_delegate_impl.h"
#include "weave_inspector.h"
namespace nl {
namespace Weave {
namespace DeviceLayer {
namespace {
using fuchsia::lowpan::ConnectivityState;
using fuchsia::lowpan::Credential;
using fuchsia::lowpan::Identity;
using fuchsia::lowpan::JoinerCommissioningParams;
using fuchsia::lowpan::JoinParams;
using fuchsia::lowpan::MAX_PROVISION_URL_LEN;
using fuchsia::lowpan::MAX_VENDOR_DATA_LEN;
using fuchsia::lowpan::MAX_VENDOR_MODEL_LEN;
using fuchsia::lowpan::MAX_VENDOR_NAME_LEN;
using fuchsia::lowpan::MAX_VENDOR_SW_VER_LEN;
using fuchsia::lowpan::ProvisioningParams;
using fuchsia::lowpan::PSKD_LEN;
using fuchsia::lowpan::Role;
using fuchsia::lowpan::device::AllCounters;
using fuchsia::lowpan::device::Counters;
using fuchsia::lowpan::device::CountersSyncPtr;
using fuchsia::lowpan::device::DeviceExtraSyncPtr;
using fuchsia::lowpan::device::DeviceState;
using fuchsia::lowpan::device::DeviceSyncPtr;
using fuchsia::lowpan::device::Lookup_LookupDevice_Result;
using fuchsia::lowpan::device::LookupSyncPtr;
using fuchsia::lowpan::device::MacCounters;
using fuchsia::lowpan::device::Protocols;
using fuchsia::lowpan::device::ProvisionError;
using fuchsia::lowpan::device::ProvisioningMonitor_WatchProgress_Result;
using fuchsia::lowpan::thread::LegacyJoiningSyncPtr;
using fuchsia::net::IpAddress;
using fuchsia::net::Ipv4Address;
using fuchsia::net::Ipv6Address;
using fuchsia::net::routes::State_Resolve_Result;
using nl::Weave::WeaveInspector;
using nl::Weave::DeviceLayer::ConfigurationManager;
using nl::Weave::DeviceLayer::ConfigurationMgr;
using nl::Weave::DeviceLayer::PlatformMgrImpl;
using nl::Weave::DeviceLayer::Internal::DeviceNetworkInfo;
using nl::Weave::Profiles::NetworkProvisioning::kNetworkType_Thread;
using ThreadDeviceType = ConnectivityManager::ThreadDeviceType;
namespace TelemetryNetworkWpanTrait = Schema::Nest::Trait::Network::TelemetryNetworkWpanTrait;
constexpr uint16_t kMinThreadChannel = 11;
constexpr uint16_t kMaxThreadChannel = 26;
// Default joinable period for Thread network setup.
constexpr zx::duration kThreadJoinableDurationDefault{zx_duration_from_sec(300)};
// A joinable duration of 0 stops any active joinable state.
constexpr zx::duration kThreadJoinableStop{zx_duration_from_sec(0)};
// The required size of a buffer supplied to GetPrimary802154MACAddress.
constexpr size_t k802154MacAddressBufSize =
sizeof(Profiles::DeviceDescription::WeaveDeviceDescriptor::Primary802154MACAddress);
// Fake MAC address returned by GetPrimary802154MACAddress
constexpr uint8_t kFakeMacAddress[k802154MacAddressBufSize] = {0xFF};
// The maximum buffer size of all info needed for joining parameters.
constexpr size_t kJoinInfoBufferSize{
MaxConstant(ConfigurationManager::kMaxPairingCodeLength + 1,
ConfigurationManager::kMaxFirmwareRevisionLength + 1,
ConfigurationManager::kMaxProductIdDescriptionLength + 1,
ConfigurationManager::kMaxVendorIdDescriptionLength + 1)};
// The duration that Thread should spend attempting to join an existing network
// at startup.
constexpr zx::duration kJoinAtStartupTimeout{zx_duration_from_sec(120)};
// The duration of delay that should occur between join attempts.
constexpr zx::duration kJoinAtStartupRetryDelay{zx_duration_from_sec(10)};
} // namespace
// Note: Since the functions within this class are intended to function
// synchronously within the Device Layer, these functions all use SyncPtrs for
// interfacing with the LoWPAN FIDL protocols.
WEAVE_ERROR ThreadStackManagerDelegateImpl::InitThreadStack() {
// See note at top to explain these SyncPtrs.
LookupSyncPtr lookup;
Lookup_LookupDevice_Result result;
Protocols protocols;
std::vector<std::string> interface_names;
zx_status_t status;
// Check whether Thread support is enabled in the ConfigurationManager
if (!ConfigurationMgrImpl().IsThreadEnabled()) {
FX_LOGS(INFO) << "Thread support is disabled for this device.";
is_thread_supported_ = false;
return WEAVE_NO_ERROR;
}
// Access the LoWPAN service.
status = PlatformMgrImpl().GetComponentContextForProcess()->svc()->Connect(lookup.NewRequest());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to fuchsia.lowpan.device.Lookup: "
<< zx_status_get_string(status);
return status;
}
// Retrieve LoWPAN interface names.
status = lookup->GetDevices(&interface_names);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to retrieve LoWPAN interface names: " << zx_status_get_string(status);
return status;
}
// Check returned interfaces for Thread support.
fuchsia::lowpan::device::DeviceSyncPtr device;
bool found_device = false;
for (auto& name : interface_names) {
std::vector<std::string> net_types;
protocols.set_device(device.NewRequest());
// Look up the device by interface name.
status = lookup->LookupDevice(name, std::move(protocols), &result);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to lookup device: " << zx_status_get_string(status);
return status;
}
if (result.is_err()) {
FX_LOGS(WARNING) << "LoWPAN service error during lookup: "
<< static_cast<int32_t>(result.err());
continue;
}
// Check if the device supports Thread.
status = device->GetSupportedNetworkTypes(&net_types);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to request supported network types from device \"" << name
<< "\": " << zx_status_get_string(status);
return status;
}
for (auto& net_type : net_types) {
if (net_type == fuchsia::lowpan::NET_TYPE_THREAD_1_X) {
// Found a Thread device.
interface_name_ = name;
found_device = true;
break;
}
}
if (found_device) {
break;
}
}
if (!found_device) {
FX_LOGS(ERROR) << "Could not find a device that supports Thread networks!";
return ZX_ERR_NOT_FOUND;
}
is_thread_supported_ = true;
if (!IsThreadProvisioned()) {
return StartThreadJoining();
}
return WEAVE_NO_ERROR;
}
bool ThreadStackManagerDelegateImpl::HaveRouteToAddress(const IPAddress& destAddr) {
fuchsia::net::routes::StateSyncPtr routes;
State_Resolve_Result result;
IpAddress netstack_addr;
zx_status_t status;
status = PlatformMgrImpl().GetComponentContextForProcess()->svc()->Connect(routes.NewRequest());
if (status != ZX_OK) {
// Unfortunately, no way to inform of error status.
return false;
}
if (destAddr.IsIPv6()) {
Ipv6Address netstack_v6_addr;
static_assert(sizeof(netstack_v6_addr.addr) == sizeof(destAddr.Addr));
std::memcpy(&netstack_v6_addr.addr, destAddr.Addr, sizeof(destAddr.Addr));
netstack_addr.set_ipv6(std::move(netstack_v6_addr));
} else if (destAddr.IsIPv4()) {
Ipv4Address netstack_v4_addr;
static_assert(sizeof(netstack_v4_addr.addr) == sizeof(destAddr.Addr[3]));
std::memcpy(&netstack_v4_addr.addr, &destAddr.Addr[3], sizeof(destAddr.Addr[3]));
netstack_addr.set_ipv4(std::move(netstack_v4_addr));
} else {
// No route to the "unspecified address".
FX_LOGS(ERROR) << "HaveRouteToAddress recieved unspecified IP address.";
return false;
}
status = routes->Resolve(std::move(netstack_addr), &result);
if (status != ZX_OK) {
// Unfortunately, no way to inform of error status.
return false;
} else if (result.is_err()) {
// Result will be ZX_ERR_ADDRESS_UNREACHABLE if unreachable.
if (result.err() != ZX_ERR_ADDRESS_UNREACHABLE) {
FX_LOGS(ERROR) << "Result from resolving route was error "
<< zx_status_get_string(result.err());
}
return false;
}
// Result resolved, a route exists.
return true;
}
void ThreadStackManagerDelegateImpl::OnPlatformEvent(const WeaveDeviceEvent* event) {}
bool ThreadStackManagerDelegateImpl::IsThreadEnabled() {
DeviceState device_state;
if (!IsThreadSupported()) {
return false;
}
// Get the device state.
if (GetDeviceState(device_state) != ZX_OK) {
return false;
}
// Determine whether Thread is enabled.
switch (device_state.connectivity_state()) {
case ConnectivityState::OFFLINE:
case ConnectivityState::ATTACHING:
case ConnectivityState::ATTACHED:
case ConnectivityState::ISOLATED:
case ConnectivityState::COMMISSIONING:
return true;
default:
return false;
}
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::SetThreadEnabled(bool val) {
DeviceSyncPtr device;
zx_status_t status;
if (!IsThreadSupported()) {
return WEAVE_ERROR_UNSUPPORTED_WEAVE_FEATURE;
}
status = GetDevice(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to acquire LoWPAN device: " << zx_status_get_string(status);
return status;
}
// Enable or disable the device.
status = device->SetActive(val);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to " << (val ? "enable" : "disable")
<< " Thread: " << zx_status_get_string(status);
return status;
}
return WEAVE_NO_ERROR;
}
bool ThreadStackManagerDelegateImpl::IsThreadProvisioned() {
DeviceState device_state;
if (!IsThreadSupported()) {
return false;
}
// Get the device state.
if (GetDeviceState(device_state) != ZX_OK) {
return false;
}
// Check for the provision.
switch (device_state.connectivity_state()) {
case ConnectivityState::INACTIVE:
case ConnectivityState::OFFLINE:
case ConnectivityState::COMMISSIONING:
return false;
default:
return true;
}
}
bool ThreadStackManagerDelegateImpl::IsThreadAttached() {
DeviceState device_state;
if (!IsThreadSupported()) {
return false;
}
// Get the device state.
if (GetDeviceState(device_state) != ZX_OK) {
return false;
}
return device_state.connectivity_state() == ConnectivityState::ATTACHED;
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetThreadProvision(DeviceNetworkInfo& netInfo,
bool includeCredentials) {
DeviceExtraSyncPtr device_extra;
Identity identity;
zx_status_t status;
if (!IsThreadSupported()) {
return WEAVE_ERROR_UNSUPPORTED_WEAVE_FEATURE;
}
if (!IsThreadProvisioned()) {
return WEAVE_ERROR_INCORRECT_STATE;
}
// Get the Device pointer.
status = GetProtocols(std::move(Protocols().set_device_extra(device_extra.NewRequest())));
if (status != ZX_OK) {
return status;
}
// Get the network identity.
status = device_extra->WatchIdentity(&identity);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Could not get LoWPAN network identity: " << zx_status_get_string(status);
return status;
}
// TODO(fxbug.dev/67254): Restore the following block once the LoWPAN service
// correctly returns the net_type.
// // Check if the provision is a Thread network.
// if (!identity.has_net_type()) {
// FX_LOGS(ERROR) << "No net_type provided; cannot confirm Thread network type.";
// return ZX_ERR_INTERNAL;
// }
// if (identity.net_type() != fuchsia::lowpan::NET_TYPE_THREAD_1_X) {
// FX_LOGS(ERROR) << "Cannot support LoWPAN network type \"" << identity.net_type()
// << "\" in ThreadStackManager.";
// return ZX_ERR_INTERNAL;
// }
// Start copying provision info.
netInfo.Reset();
netInfo.NetworkType = kNetworkType_Thread;
netInfo.NetworkId = Internal::kThreadNetworkId;
netInfo.FieldPresent.NetworkId = true;
// Copy network name.
if (identity.has_raw_name()) {
std::memcpy(netInfo.ThreadNetworkName, identity.raw_name().data(),
std::min<size_t>(DeviceNetworkInfo::kMaxThreadNetworkNameLength,
identity.raw_name().size()));
}
// Copy extended PAN id.
if (identity.has_xpanid()) {
std::memcpy(
netInfo.ThreadExtendedPANId, identity.xpanid().data(),
std::min<size_t>(DeviceNetworkInfo::kThreadExtendedPANIdLength, identity.xpanid().size()));
netInfo.FieldPresent.ThreadExtendedPANId = true;
}
// Copy PAN id.
if (!identity.has_panid()) {
// Warn that PAN id remains unspecified.
FX_LOGS(WARNING) << "PAN id not supplied.";
} else {
netInfo.ThreadPANId = identity.panid();
}
// Copy channel.
if (!identity.has_channel() || identity.channel() < kMinThreadChannel ||
identity.channel() > kMaxThreadChannel) {
// Warn that channel remains unspecified.
std::string channel_info =
identity.has_channel() ? std::to_string(identity.channel()) : "(none)";
FX_LOGS(WARNING) << "Invalid Thread channel: " << channel_info;
} else {
netInfo.ThreadChannel = identity.channel();
}
// TODO(fxbug.dev/55638) - Implement mesh prefix and pre-shared commisioning key.
if (!includeCredentials) {
// No futher processing needed, credentials won't be included.
return WEAVE_NO_ERROR;
}
// Get credential.
std::unique_ptr<Credential> credential;
status = device_extra->GetCredential(&credential);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Could not retrieve credential: " << zx_status_get_string(status);
return status;
}
// Copy credential info.
if (!credential) {
// Warn that credential remains unset.
FX_LOGS(WARNING) << "Credential requested but no credential provided from LoWPAN device";
} else {
std::memcpy(netInfo.ThreadNetworkKey, credential->master_key().data(),
std::min<size_t>(DeviceNetworkInfo::kMaxThreadNetworkNameLength,
credential->master_key().size()));
netInfo.FieldPresent.ThreadNetworkKey = true;
}
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::SetThreadProvision(const DeviceNetworkInfo& netInfo) {
DeviceSyncPtr device;
std::unique_ptr<Credential> credential;
Identity identity;
if (!IsThreadSupported()) {
return WEAVE_ERROR_UNSUPPORTED_WEAVE_FEATURE;
}
// Cancel join operation, if active:
StopThreadJoining();
// Set up identity.
std::vector<uint8_t> network_name{
netInfo.ThreadNetworkName,
netInfo.ThreadNetworkName + std::strlen(netInfo.ThreadNetworkName)};
identity.set_raw_name(std::move(network_name));
identity.set_net_type(fuchsia::lowpan::NET_TYPE_THREAD_1_X);
if (netInfo.FieldPresent.ThreadExtendedPANId) {
identity.set_xpanid(std::vector<uint8_t>{
netInfo.ThreadExtendedPANId,
netInfo.ThreadExtendedPANId + DeviceNetworkInfo::kThreadExtendedPANIdLength});
} else {
FX_LOGS(ERROR) << "No XPAN ID provided to SetThreadProvision.";
return WEAVE_ERROR_INVALID_ARGUMENT;
}
if (netInfo.ThreadChannel != Profiles::NetworkProvisioning::kThreadChannel_NotSpecified) {
identity.set_channel(netInfo.ThreadChannel);
} else {
FX_LOGS(ERROR) << "No channel provided to SetThreadProvision.";
return WEAVE_ERROR_INVALID_ARGUMENT;
}
if (netInfo.ThreadPANId != Profiles::NetworkProvisioning::kThreadPANId_NotSpecified) {
identity.set_panid(netInfo.ThreadPANId);
} else {
FX_LOGS(ERROR) << "No PAN ID provided to SetThreadProvision.";
return WEAVE_ERROR_INVALID_ARGUMENT;
}
// Set up credential.
if (netInfo.FieldPresent.ThreadNetworkKey) {
credential = std::make_unique<Credential>();
credential->set_master_key(std::vector<uint8_t>{
netInfo.ThreadNetworkKey,
netInfo.ThreadNetworkKey + DeviceNetworkInfo::kThreadNetworkKeyLength});
} else {
FX_LOGS(ERROR) << "No network key provided to SetThreadProvision.";
return WEAVE_ERROR_INVALID_ARGUMENT;
}
// Add identity and credential to provisioning params.
ProvisioningParams params{.identity = std::move(identity), .credential = std::move(credential)};
// Acquire the thread device.
zx_status_t status = GetDevice(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to acquire LoWPAN device: " << zx_status_get_string(status);
return status;
}
// Provision the thread device.
status = device->ProvisionNetwork(std::move(params));
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to provision network: " << zx_status_get_string(status);
return status;
}
auto& inspector = WeaveInspector::GetWeaveInspector();
inspector.NotifyPairingStateChange(WeaveInspector::kPairingState_ThreadNetworkCreatedOrJoined);
return WEAVE_NO_ERROR;
}
void ThreadStackManagerDelegateImpl::ClearThreadProvision() {
DeviceSyncPtr device;
zx_status_t status;
if (!IsThreadSupported()) {
return;
}
// Cancel join operation, if active:
StopThreadJoining();
// Acquire the thread device.
status = GetDevice(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to acquire LoWPAN device: " << zx_status_get_string(status);
return;
}
status = device->LeaveNetwork();
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Could not clear LoWPAN provision: " << zx_status_get_string(status);
}
}
ThreadDeviceType ThreadStackManagerDelegateImpl::GetThreadDeviceType() {
DeviceState device_state;
// Get the device state.
if (GetDeviceState(device_state) != ZX_OK || !IsThreadSupported()) {
return ThreadDeviceType::kThreadDeviceType_NotSupported;
}
// Determine device type by role.
switch (device_state.role()) {
case Role::END_DEVICE:
return ThreadDeviceType::kThreadDeviceType_FullEndDevice;
case Role::SLEEPY_END_DEVICE:
return ThreadDeviceType::kThreadDeviceType_SleepyEndDevice;
case Role::ROUTER:
case Role::SLEEPY_ROUTER:
case Role::LEADER:
case Role::COORDINATOR:
return ThreadDeviceType::kThreadDeviceType_Router;
default:
return ThreadDeviceType::kThreadDeviceType_NotSupported;
};
}
bool ThreadStackManagerDelegateImpl::HaveMeshConnectivity() { return IsThreadAttached(); }
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetAndLogThreadStatsCounters() {
nl::Weave::Profiles::DataManagement_Current::event_id_t event_id;
TelemetryNetworkWpanTrait::NetworkWpanStatsEvent counter_event = {};
zx_status_t status;
// Get LoWPAN protocols.
CountersSyncPtr counters;
DeviceExtraSyncPtr device_extra;
status = GetProtocols(std::move(
Protocols().set_counters(counters.NewRequest()).set_device_extra(device_extra.NewRequest())));
if (status != ZX_OK) {
return status;
}
// Get MAC counters.
AllCounters all_counters;
status = counters->Get(&all_counters);
if (status != ZX_OK) {
return status;
}
// MAC TX counters.
if (all_counters.has_mac_tx()) {
const MacCounters& mac_tx = all_counters.mac_tx();
counter_event.phyTx = mac_tx.total();
counter_event.macUnicastTx = mac_tx.unicast();
counter_event.macBroadcastTx = mac_tx.broadcast();
counter_event.macTxAckReq = mac_tx.ack_requested();
counter_event.macTxNoAckReq = mac_tx.no_ack_requested();
counter_event.macTxAcked = mac_tx.acked();
counter_event.macTxData = mac_tx.data();
counter_event.macTxDataPoll = mac_tx.data_poll();
counter_event.macTxBeacon = mac_tx.beacon();
counter_event.macTxBeaconReq = mac_tx.beacon_request();
counter_event.macTxOtherPkt = mac_tx.other();
counter_event.macTxRetry = mac_tx.retries();
counter_event.macTxFailCca = mac_tx.err_cca();
}
// MAC RX counters.
if (all_counters.has_mac_rx()) {
const MacCounters& mac_rx = all_counters.mac_rx();
counter_event.phyRx = mac_rx.total();
counter_event.macUnicastRx = mac_rx.unicast();
counter_event.macBroadcastRx = mac_rx.broadcast();
counter_event.macRxData = mac_rx.data();
counter_event.macRxDataPoll = mac_rx.data_poll();
counter_event.macRxBeacon = mac_rx.beacon();
counter_event.macRxBeaconReq = mac_rx.beacon_request();
counter_event.macRxOtherPkt = mac_rx.other();
counter_event.macRxFilterWhitelist = mac_rx.address_filtered();
counter_event.macRxFilterDestAddr = mac_rx.dest_addr_filtered();
// Rx Error Counters
counter_event.macRxFailDecrypt = mac_rx.err_sec();
counter_event.macRxFailNoFrame = mac_rx.err_no_frame();
counter_event.macRxFailUnknownNeighbor = mac_rx.err_unknown_neighbor();
counter_event.macRxFailInvalidSrcAddr = mac_rx.err_invalid_src_addr();
counter_event.macRxFailFcs = mac_rx.err_fcs();
counter_event.macRxFailOther = mac_rx.err_other();
}
// Thread channel.
Identity identity;
status = device_extra->WatchIdentity(&identity);
if (status != ZX_OK) {
return status;
}
if (identity.has_channel()) {
counter_event.channel = identity.channel();
}
// Node type.
DeviceState device_state;
status = GetDeviceState(device_state);
if (status != ZX_OK) {
return status;
}
switch (device_state.role()) {
case Role::LEADER:
counter_event.nodeType |= TelemetryNetworkWpanTrait::NODE_TYPE_LEADER;
// Fallthrough intentional, leaders are also routers.
__FALLTHROUGH;
case Role::ROUTER:
case Role::SLEEPY_ROUTER:
case Role::COORDINATOR:
counter_event.nodeType |= TelemetryNetworkWpanTrait::NODE_TYPE_ROUTER;
break;
default:
counter_event.nodeType = 0;
break;
}
FX_LOGS(DEBUG) << "Rx Counters:\n"
<< " PHY Rx Total: " << counter_event.phyRx << "\n"
<< " MAC Rx Unicast: " << counter_event.macUnicastRx << "\n"
<< " MAC Rx Broadcast: " << counter_event.macBroadcastRx << "\n"
<< " MAC Rx Data: " << counter_event.macRxData << "\n"
<< " MAC Rx Data Polls: " << counter_event.macRxDataPoll << "\n"
<< " MAC Rx Beacons: " << counter_event.macRxBeacon << "\n"
<< " MAC Rx Beacon Reqs: " << counter_event.macRxBeaconReq << "\n"
<< " MAC Rx Other: " << counter_event.macRxOtherPkt << "\n"
<< " MAC Rx Filtered List: " << counter_event.macRxFilterWhitelist << "\n"
<< " MAC Rx Filtered Addr: " << counter_event.macRxFilterDestAddr << "\n"
<< "\n"
<< "Tx Counters:\n"
<< " PHY Tx Total: " << counter_event.phyTx << "\n"
<< " MAC Tx Unicast: " << counter_event.macUnicastTx << "\n"
<< " MAC Tx Broadcast: " << counter_event.macBroadcastTx << "\n"
<< " MAC Tx Data: " << counter_event.macTxData << "\n"
<< " MAC Tx Data Polls: " << counter_event.macTxDataPoll << "\n"
<< " MAC Tx Beacons: " << counter_event.macTxBeacon << "\n"
<< " MAC Tx Beacon Reqs: " << counter_event.macTxBeaconReq << "\n"
<< " MAC Tx Other: " << counter_event.macTxOtherPkt << "\n"
<< " MAC Tx Retry: " << counter_event.macTxRetry << "\n"
<< " MAC Tx CCA Fail: " << counter_event.macTxFailCca << "\n"
<< "\n"
<< "Failure Counters:\n"
<< " MAC Rx Decrypt Fail: " << counter_event.macRxFailDecrypt << "\n"
<< " MAC Rx No Frame: " << counter_event.macRxFailNoFrame << "\n"
<< " MAC Rx Unkwn Neighbor: " << counter_event.macRxFailUnknownNeighbor << "\n"
<< " MAC Rx Invalid Src Addr: " << counter_event.macRxFailInvalidSrcAddr << "\n"
<< " MAC Rx FCS Fail: " << counter_event.macRxFailFcs << "\n"
<< " MAC Rx Other Fail: " << counter_event.macRxFailOther << "\n";
event_id = LogNetworkWpanStatsEvent(&counter_event);
FX_LOGS(DEBUG) << "Thread telemetry stats event ID: " << event_id << ".";
return status;
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetAndLogThreadTopologyMinimal() {
return WEAVE_ERROR_NOT_IMPLEMENTED; // TODO(fxbug.dev/55888)
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetAndLogThreadTopologyFull() {
return WEAVE_ERROR_NOT_IMPLEMENTED; // TODO(fxbug.dev/55888)
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetPrimary802154MACAddress(uint8_t* mac_address) {
if (!IsThreadSupported()) {
return WEAVE_ERROR_UNSUPPORTED_WEAVE_FEATURE;
}
// This is setting the MAC address to FF:0:0:0:0:0:0:0; this is for a few reasons:
// 1. The actual value of the MAC address in the descriptor is not currently used.
// 2. The MAC address (either the factory or the current address) is PII, so it should not be
// transmitted unless necessary.
// 3. Some value should still be transmitted as some tools or other devices use the presence of
// an 802.15.4 MAC address to determine if Thread is supported.
// The best way to meet these requirements is to provide a faked-out MAC address instead.
std::memcpy(mac_address, kFakeMacAddress, k802154MacAddressBufSize);
return WEAVE_NO_ERROR;
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::SetThreadJoinable(bool enable) {
LegacyJoiningSyncPtr thread_legacy;
zx_status_t status;
// Cancel join operation, if active:
StopThreadJoining();
// Get the legacy Thread protocol
status =
GetProtocols(std::move(Protocols().set_thread_legacy_joining(thread_legacy.NewRequest())));
if (status != ZX_OK) {
return status;
}
// Check for configured duration.
uint32_t duration_sec;
zx::duration duration;
WEAVE_ERROR err = ConfigurationMgrImpl().GetThreadJoinableDuration(&duration_sec);
if (err == WEAVE_DEVICE_ERROR_CONFIG_NOT_FOUND) {
FX_LOGS(INFO) << "No joinable duration specified, using default duration.";
duration = kThreadJoinableDurationDefault;
} else if (err == WEAVE_NO_ERROR) {
duration = zx::duration{zx_duration_from_sec(duration_sec)};
} else {
FX_LOGS(ERROR) << "Error reading Thread joinable duration config value.";
return err;
}
// Set joinable or disable joinable based on the intended value.
if (enable) {
FX_LOGS(INFO) << "Requesting Thread radio joinable state for " << duration.to_secs()
<< " seconds.";
} else {
FX_LOGS(INFO) << "Requesting to disable Thread radio joinable state.";
}
status = thread_legacy->MakeJoinable(enable ? duration.get() : kThreadJoinableStop.get(),
WEAVE_UNSECURED_PORT);
if (status != ZX_OK) {
return status;
}
// Confirm joinable state has been updated successfully.
return WEAVE_NO_ERROR;
}
zx_status_t ThreadStackManagerDelegateImpl::GetDevice(DeviceSyncPtr& device) {
return GetProtocols(std::move(Protocols().set_device(device.NewRequest())));
}
zx_status_t ThreadStackManagerDelegateImpl::GetDeviceState(DeviceState& device_state) {
DeviceSyncPtr device;
zx_status_t status;
status = GetDevice(device);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to acquire LoWPAN device: " << zx_status_get_string(status);
return status;
}
status = device->WatchDeviceState(&device_state);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Could not get LoWPAN device state: " << zx_status_get_string(status);
return status;
}
return ZX_OK;
}
zx_status_t ThreadStackManagerDelegateImpl::GetProtocols(Protocols protocols) {
// See note at top to explain these SyncPtrs.
LookupSyncPtr lookup;
Lookup_LookupDevice_Result result;
zx_status_t status;
// Access the LoWPAN service.
status = PlatformMgrImpl().GetComponentContextForProcess()->svc()->Connect(lookup.NewRequest());
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to fuchsia.lowpan.device.Lookup: "
<< zx_status_get_string(status);
return status;
}
// Look up the device by interface name.
status = lookup->LookupDevice(interface_name_, std::move(protocols), &result);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to lookup device: " << zx_status_get_string(status);
return status;
}
if (result.is_err()) {
FX_LOGS(ERROR) << "LoWPAN service error during lookup: " << static_cast<int32_t>(result.err());
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
std::string ThreadStackManagerDelegateImpl::GetInterfaceName() const { return interface_name_; }
bool ThreadStackManagerDelegateImpl::IsThreadSupported() const { return is_thread_supported_; }
nl::Weave::Profiles::DataManagement::event_id_t
ThreadStackManagerDelegateImpl::LogNetworkWpanStatsEvent(
Schema::Nest::Trait::Network::TelemetryNetworkWpanTrait::NetworkWpanStatsEvent* event) {
return nl::LogEvent(event);
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::StartThreadJoining() {
WEAVE_ERROR err =
StartJoiningTimeout(kJoinAtStartupTimeout.to_msecs(), [this] { HandleJoiningTimeout(); });
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << "Could not post timeout, cancelling join.";
StopThreadJoining();
return err;
}
return StartThreadJoiningIteration();
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::StartThreadJoiningIteration() {
JoinParams join_params;
zx_status_t status = ZX_OK;
DeviceExtraSyncPtr device;
if (provisioning_monitor_) {
FX_LOGS(ERROR) << "Already attempting to join.";
return WEAVE_ERROR_INCORRECT_STATE;
}
status = GetProtocols(std::move(Protocols().set_device_extra(device.NewRequest())));
if (status != ZX_OK || !device) {
FX_LOGS(ERROR) << "Could not start Thread joining: Could not get DeviceExtra protocol.";
return status;
}
WEAVE_ERROR err = GetJoinParams(join_params);
if (err != WEAVE_NO_ERROR) {
return err;
}
FX_LOGS(INFO) << "Requesting Thread network join.";
status = device->JoinNetwork(std::move(join_params), provisioning_monitor_.NewRequest());
if (status != ZX_OK || !provisioning_monitor_.is_bound()) {
FX_LOGS(ERROR) << "Could not begin join network attempt.";
return status;
}
provisioning_monitor_.set_error_handler([this](zx_status_t status) {
if (status != ZX_OK) {
// If the channel closed unexpectedly, continue as if WatchProgress had completed with a
// failure condition.
HandleProvisioningProgress(
ProvisioningMonitor_WatchProgress_Result::WithErr(ProvisionError::NETWORK_NOT_FOUND));
}
});
provisioning_monitor_->WatchProgress(
fit::bind_member(this, &ThreadStackManagerDelegateImpl::HandleProvisioningProgress));
return WEAVE_NO_ERROR;
}
void ThreadStackManagerDelegateImpl::StopThreadJoining() {
FX_LOGS(INFO) << "Stopping Thread network joining.";
provisioning_monitor_.Unbind();
CancelJoiningTimeout();
CancelJoiningRetry();
}
void ThreadStackManagerDelegateImpl::CancelJoiningTimeout() { joining_timeout_.Cancel(); }
void ThreadStackManagerDelegateImpl::CancelJoiningRetry() { joining_retry_delay_.Cancel(); }
WEAVE_ERROR ThreadStackManagerDelegateImpl::StartJoiningTimeout(uint32_t delay_milliseconds,
fit::closure callback) {
joining_timeout_.set_handler(std::move(callback));
return joining_timeout_.PostDelayed(PlatformMgrImpl().GetDispatcher(),
zx::msec(delay_milliseconds));
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::StartJoiningRetry(uint32_t delay_milliseconds,
fit::closure callback) {
joining_retry_delay_.set_handler(std::move(callback));
return joining_retry_delay_.PostDelayed(PlatformMgrImpl().GetDispatcher(),
zx::msec(delay_milliseconds));
}
WEAVE_ERROR ThreadStackManagerDelegateImpl::GetJoinParams(JoinParams& join_params) {
WEAVE_ERROR err = WEAVE_NO_ERROR;
JoinerCommissioningParams commissioning_params;
size_t config_length_out;
char config_buffer[kJoinInfoBufferSize];
// Copy pairing code as PSKd.
err = ConfigurationMgr().GetPairingCode(config_buffer, kJoinInfoBufferSize, config_length_out);
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << " Failed to get pairing code: " << nl::ErrorStr(err) << ".";
return err;
}
if (config_length_out > PSKD_LEN) {
FX_LOGS(WARNING) << "PSKd was too long, truncating to " << PSKD_LEN << " bytes.";
config_length_out = PSKD_LEN;
}
commissioning_params.set_pskd(std::string(config_buffer, config_length_out));
// Copy vendor name.
err = ConfigurationMgr().GetVendorIdDescription(config_buffer, kJoinInfoBufferSize,
config_length_out);
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << " Failed to get vendor description: " << nl::ErrorStr(err) << ".";
return err;
}
if (config_length_out > MAX_VENDOR_NAME_LEN) {
FX_LOGS(WARNING) << "Vendor name was too long, truncating to " << MAX_VENDOR_NAME_LEN
<< " bytes.";
config_length_out = MAX_VENDOR_NAME_LEN;
}
commissioning_params.set_vendor_name(std::string(config_buffer, config_length_out));
// Copy vendor model.
err = ConfigurationMgr().GetProductIdDescription(config_buffer, kJoinInfoBufferSize,
config_length_out);
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << " Failed to get product description: " << nl::ErrorStr(err) << ".";
return err;
}
if (config_length_out > MAX_VENDOR_MODEL_LEN) {
FX_LOGS(WARNING) << "Vendor model was too long, truncating to " << MAX_VENDOR_MODEL_LEN
<< " bytes.";
config_length_out = MAX_VENDOR_MODEL_LEN;
}
commissioning_params.set_vendor_model(std::string(config_buffer, config_length_out));
// Copy vendor software version.
err =
ConfigurationMgr().GetFirmwareRevision(config_buffer, kJoinInfoBufferSize, config_length_out);
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << " Failed to get software version: " << nl::ErrorStr(err) << ".";
return err;
}
if (config_length_out > MAX_VENDOR_SW_VER_LEN) {
FX_LOGS(WARNING) << "Vendor SW version was too long, truncating to " << MAX_VENDOR_SW_VER_LEN
<< " bytes.";
config_length_out = MAX_VENDOR_SW_VER_LEN;
}
commissioning_params.set_vendor_sw_version(std::string(config_buffer, config_length_out));
join_params.set_joiner_parameter(std::move(commissioning_params));
return WEAVE_NO_ERROR;
}
void ThreadStackManagerDelegateImpl::HandleJoiningTimeout() {
joining_timeout_expired_ = true;
if (!joining_in_progress_) {
FX_LOGS(INFO) << "Thread joining attempt timed out, stopping join.";
StopThreadJoining();
} else {
FX_LOGS(INFO) << "Thread joining timeout occurred, but not stoping in-progress join.";
}
}
void ThreadStackManagerDelegateImpl::HandleJoiningRetryDelay() {
if (!joining_timeout_expired_) {
FX_LOGS(INFO) << "Retrying Thread network join.";
StartThreadJoiningIteration();
} else {
FX_LOGS(INFO) << "Thread joining timeout occured before retry.";
}
}
void ThreadStackManagerDelegateImpl::HandleProvisioningProgress(
ProvisioningMonitor_WatchProgress_Result result) {
if (result.is_err() && result.err() != ProvisionError::CANCELED) {
joining_in_progress_ = false;
provisioning_monitor_.Unbind();
// Join failed but not cancelled, delay and retry.
if (!joining_timeout_expired_) {
FX_LOGS(INFO) << "Did not join network, waiting before retry.";
StartJoiningRetry(kJoinAtStartupRetryDelay.to_msecs(), [this] { HandleJoiningRetryDelay(); });
}
return;
}
if (result.is_err() && result.err() == ProvisionError::CANCELED) {
FX_LOGS(INFO) << "Join operation canceled.";
StopThreadJoining();
return;
}
if (result.response().progress.is_progress()) {
if (!joining_in_progress_) {
joining_in_progress_ = true;
FX_LOGS(INFO) << "Thread joining in progress.";
}
provisioning_monitor_->WatchProgress(
fit::bind_member(this, &ThreadStackManagerDelegateImpl::HandleProvisioningProgress));
return;
}
if (result.response().progress.is_identity()) {
FX_LOGS(INFO) << "Thread joining attempt completed.";
StopThreadJoining();
}
}
} // namespace DeviceLayer
} // namespace Weave
} // namespace nl