blob: d4c38131d6e0f92224486bcf217dfbf03b2423b8 [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 "stack_impl.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <string>
// clang-format off
#pragma GCC diagnostic push
#include <Weave/DeviceLayer/PlatformManager.h>
#include <Weave/DeviceLayer/ConfigurationManager.h>
#pragma GCC diagnostic pop
// clang-format on
#include "stack_utils.h"
namespace weavestack {
namespace {
using fuchsia::weave::ErrorCode;
using fuchsia::weave::HostPort;
using fuchsia::weave::PairingState;
using fuchsia::weave::PairingStateWatcher;
using fuchsia::weave::QrCode;
using fuchsia::weave::ResetConfigFlags;
using fuchsia::weave::Stack_GetQrCode_Response;
using fuchsia::weave::Stack_GetQrCode_Result;
using fuchsia::weave::Stack_ResetConfig_Response;
using fuchsia::weave::Stack_ResetConfig_Result;
using fuchsia::weave::SvcDirectoryWatcher;
using nl::Weave::DeviceLayer::ConfigurationMgr;
using nl::Weave::DeviceLayer::PlatformMgrImpl;
using nl::Weave::DeviceLayer::WeaveDeviceEvent;
using nl::Weave::DeviceLayer::DeviceEventType::kAccountPairingChange;
using nl::Weave::DeviceLayer::DeviceEventType::kFabricMembershipChange;
using nl::Weave::DeviceLayer::DeviceEventType::kServiceProvisioningChange;
using nl::Weave::DeviceLayer::DeviceEventType::kServiceTunnelStateChange;
using nl::Weave::Profiles::DeviceControl::DeviceControlDelegate;
// Size of the buffer for retrieving the QR code.
constexpr size_t kQrCodeBufSize = fuchsia::weave::MAX_QR_CODE_SIZE + 1;
// Size of the buffer for retrieving hostnames.
constexpr size_t kHostnameBufferSize = fuchsia::net::MAX_HOSTNAME_SIZE + 1;
} // namespace
// Watcher class declarations --------------------------------------------------
class StackImpl::PairingStateWatcherImpl : public PairingStateWatcher {
public:
explicit PairingStateWatcherImpl(StackImpl* stack);
/// Returns the state of pairing
///
/// First call returns the current pairing state or blocks until the pairing
/// state is available. Subsequent calls will block until the pairing state
/// changes.
void WatchPairingState(WatchPairingStateCallback callback) override;
/// Notify that pairing state has changed.
zx_status_t Notify();
private:
/// Perform the callback if there is an active watcher.
zx_status_t DoCallback();
// Prevent copy construction.
PairingStateWatcherImpl(const PairingStateWatcherImpl&) = delete;
// Prevent copy assignment.
PairingStateWatcherImpl& operator=(const PairingStateWatcherImpl&) = delete;
StackImpl* stack_;
bool dirty_ = false;
bool first_call_ = true;
WatchPairingStateCallback pairing_state_callback_;
};
class StackImpl::SvcDirectoryWatcherImpl : public SvcDirectoryWatcher {
public:
explicit SvcDirectoryWatcherImpl(StackImpl* stack, uint64_t endpoint_id);
/// Returns a vector of HostPorts for the watched endpoint ID.
///
/// First call returns the current list of HostPorts or blocks until the list
/// is available from the service. Subsequent calls will block until a new
/// ServiceDirectory lookup is made and will return the list associated with
/// the watched endpoint ID, which may or may not be the same as prior values.
void WatchServiceDirectory(WatchServiceDirectoryCallback callback) override;
/// Notify that watched service directory has changed.
zx_status_t Notify();
private:
/// Perform the callback if there is an active watcher.
zx_status_t DoCallback();
// Prevent copy construction.
SvcDirectoryWatcherImpl(const SvcDirectoryWatcherImpl&) = delete;
// Prevent copy assignment.
SvcDirectoryWatcherImpl& operator=(const SvcDirectoryWatcherImpl&) = delete;
StackImpl* stack_;
bool dirty_ = false;
bool first_call_ = true;
uint64_t endpoint_id_;
WatchServiceDirectoryCallback svc_directory_callback_;
};
// StackImpl definitions -------------------------------------------------------
StackImpl::StackImpl(sys::ComponentContext* context) : context_(context) {}
zx_status_t StackImpl::Init() {
zx_status_t status = ZX_OK;
WEAVE_ERROR err = WEAVE_NO_ERROR;
// Register with the context.
status = context_->outgoing()->AddPublicService(bindings_.GetHandler(this));
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to register StackImpl handler with status ="
<< zx_status_get_string(status);
return status;
}
// Register event handler with Weave Device Layer.
err = PlatformMgrImpl().AddEventHandler(TrampolineEvent, reinterpret_cast<intptr_t>(this));
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << "Failed to register event handler with device layer: " << nl::ErrorStr(err);
return ZX_ERR_INTERNAL;
}
return status;
}
// The destructor must be defined after the full definitions of the watcher
// classes to avoid attempting to use an incomplete definition in the compiler
// generated default destructor.
StackImpl::~StackImpl() = default;
void StackImpl::GetPairingStateWatcher(fidl::InterfaceRequest<PairingStateWatcher> watcher) {
pairing_state_watchers_.AddBinding(std::make_unique<PairingStateWatcherImpl>(this),
std::move(watcher));
}
void StackImpl::GetSvcDirectoryWatcher(uint64_t endpoint_id,
fidl::InterfaceRequest<SvcDirectoryWatcher> watcher) {
svc_directory_watchers_.AddBinding(std::make_unique<SvcDirectoryWatcherImpl>(this, endpoint_id),
std::move(watcher));
}
void StackImpl::GetQrCode(GetQrCodeCallback callback) {
QrCode qr_code;
Stack_GetQrCode_Response response;
Stack_GetQrCode_Result result;
WEAVE_ERROR err;
// Initialize space for the QR code.
response.qr_code.data.resize(kQrCodeBufSize, '\0');
// Copy the QR code into qr_code.data.
err = ConfigurationMgr().GetQRCodeString(response.qr_code.data.data(),
response.qr_code.data.size());
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << "Failed to retrieve QR code: " << err;
result.set_err(ErrorCode::UNSPECIFIED_ERROR);
} else {
// Resize to returned length.
response.qr_code.data.resize(strnlen(response.qr_code.data.data(), kQrCodeBufSize));
result.set_response(response);
}
callback(std::move(result));
}
void StackImpl::ResetConfig(ResetConfigFlags flags, ResetConfigCallback callback) {
Stack_ResetConfig_Response response;
Stack_ResetConfig_Result result;
WEAVE_ERROR err = GetDeviceControl().OnResetConfig(static_cast<uint16_t>(flags));
if (err != WEAVE_NO_ERROR) {
result.set_err(ErrorCode::UNSPECIFIED_ERROR);
} else {
result.set_response(response);
}
callback(std::move(result));
}
void StackImpl::NotifyPairingState() {
last_pairing_state_ = std::make_unique<PairingState>(CurrentPairingState());
for (auto& binding : pairing_state_watchers_.bindings()) {
binding->impl()->Notify();
}
}
void StackImpl::NotifySvcDirectory() {
for (auto& binding : svc_directory_watchers_.bindings()) {
binding->impl()->Notify();
}
}
void StackImpl::HandleWeaveDeviceEvent(const WeaveDeviceEvent* event) {
// Handle events.
switch (event->Type) {
case kServiceTunnelStateChange:
if (event->ServiceTunnelStateChange.Result ==
nl::Weave::DeviceLayer::kConnectivity_Established) {
// New tunnel established, notify service directory watchers to check
// for updates to their service directory entries.
NotifySvcDirectory();
}
// TODO(https://fxbug.dev/42130292): Add event for Thread provisioning.
// TODO(https://fxbug.dev/42130293): Add event for WiFi provisioning.
__FALLTHROUGH;
case kFabricMembershipChange:
case kServiceProvisioningChange:
case kAccountPairingChange:
// Pairing/provisioning state has changed, notify watchers.
NotifyPairingState();
break;
default:
// Ignore all other events.
break;
}
}
void StackImpl::TrampolineEvent(const WeaveDeviceEvent* event, intptr_t arg) {
StackImpl* self = reinterpret_cast<StackImpl*>(arg);
self->HandleWeaveDeviceEvent(event);
}
DeviceControlDelegate& StackImpl::GetDeviceControl() {
return PlatformMgrImpl().GetDeviceControl();
}
zx_status_t StackImpl::LookupHostPorts(uint64_t endpoint_id, std::vector<HostPort>* host_ports) {
WEAVE_ERROR err = WEAVE_NO_ERROR;
nl::Weave::HostPortList host_port_list;
// Lookup host endpoints from service directory.
err = PlatformMgrImpl().GetServiceDirectoryManager().lookup(endpoint_id, &host_port_list);
if (err == WEAVE_ERROR_INVALID_SERVICE_EP) {
FX_LOGS(WARNING) << "Invalid service directory endpoint: " << endpoint_id;
return ZX_ERR_INVALID_ARGS;
}
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << "Failed to lookup service directory: " << nl::ErrorStr(err);
return ZX_ERR_INTERNAL;
}
while (!host_port_list.IsEmpty()) {
HostPort host_port;
char hostname_buf[kHostnameBufferSize] = {};
uint16_t port = 0;
// Extract HostPort info from host_port_list.
err = host_port_list.Pop(hostname_buf, kHostnameBufferSize, port);
if (err != WEAVE_NO_ERROR) {
FX_LOGS(ERROR) << "Failed to extract host/port in HostPortList: " << nl::ErrorStr(err);
return ZX_ERR_INTERNAL;
}
// Convert to FIDL HostPort.
host_port.host = HostFromHostname(hostname_buf);
host_port.port = port;
// Push onto returned HostPort vector.
host_ports->emplace_back(std::move(host_port));
}
return ZX_OK;
}
// Watcher class definitions ---------------------------------------------------
StackImpl::PairingStateWatcherImpl::PairingStateWatcherImpl(StackImpl* stack) : stack_(stack) {}
void StackImpl::PairingStateWatcherImpl::WatchPairingState(WatchPairingStateCallback callback) {
// Check to make sure there isn't a waiting callback already.
if (pairing_state_callback_) {
stack_->pairing_state_watchers_.CloseBinding(this, ZX_ERR_BAD_STATE);
return;
}
// Save callback for sending result (potentially async).
pairing_state_callback_ = std::move(callback);
// Callback to caller if data is ready.
DoCallback();
}
zx_status_t StackImpl::PairingStateWatcherImpl::Notify() {
// Mark dirty.
dirty_ = true;
// Perform callback if there is a waiting client.
return DoCallback();
}
zx_status_t StackImpl::PairingStateWatcherImpl::DoCallback() {
zx_status_t status = ZX_OK;
PairingState pairing_state;
// Only perform callback if ready.
if (!pairing_state_callback_ || !(dirty_ || first_call_)) {
return ZX_OK;
}
// Allocate pairing state if not present.
if (!stack_->last_pairing_state_) {
stack_->last_pairing_state_ = std::make_unique<PairingState>(CurrentPairingState());
}
// Reset for next callback.
WatchPairingStateCallback callback = std::move(pairing_state_callback_);
dirty_ = false;
first_call_ = false;
// Copy the pairing state into return value.
status = stack_->last_pairing_state_->Clone(&pairing_state);
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to clone pairing state: " << zx_status_get_string(status)
<< ", cannot send callback!";
stack_->pairing_state_watchers_.CloseBinding(this, status);
return status;
}
// Send updated pairing state to caller.
callback(std::move(pairing_state));
return ZX_OK;
}
StackImpl::SvcDirectoryWatcherImpl::SvcDirectoryWatcherImpl(StackImpl* stack, uint64_t endpoint_id)
: stack_(stack), endpoint_id_(endpoint_id) {}
void StackImpl::SvcDirectoryWatcherImpl::WatchServiceDirectory(
WatchServiceDirectoryCallback callback) {
// Check to make sure there isn't a waiting callback already.
if (svc_directory_callback_) {
stack_->svc_directory_watchers_.CloseBinding(this, ZX_ERR_BAD_STATE);
return;
}
// Save callback for sending result (potentially async).
svc_directory_callback_ = std::move(callback);
// Attempt to reply if first call or dirty data.
DoCallback();
}
zx_status_t StackImpl::SvcDirectoryWatcherImpl::Notify() {
// Mark dirty.
dirty_ = true;
// Perform callback if there is a waiting client.
return DoCallback();
}
zx_status_t StackImpl::SvcDirectoryWatcherImpl::DoCallback() {
std::vector<HostPort> host_ports;
if (!svc_directory_callback_ || !(dirty_ || first_call_)) {
return ZX_OK;
}
// Reset for next callback.
WatchServiceDirectoryCallback callback = std::move(svc_directory_callback_);
dirty_ = false;
first_call_ = false;
// Lookup the (potentially new) HostPorts.
zx_status_t status = stack_->LookupHostPorts(endpoint_id_, &host_ports);
if (status != ZX_OK) {
stack_->svc_directory_watchers_.CloseBinding(this, status);
return status;
}
// Call callback.
first_call_ = false;
callback(std::move(host_ports));
return ZX_OK;
}
} // namespace weavestack