blob: 5ef48b76fd058fc0e4eba7db34d21d2309ce2a7e [file] [log] [blame]
// Copyright 2021 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 "port_watcher.h"
#include <fbl/auto_lock.h>
#include "log.h"
namespace network::internal {
zx_status_t PortWatcher::Bind(async_dispatcher_t* dispatcher,
cpp20::span<const netdev::wire::PortId> existing_ports,
fidl::ServerEnd<netdev::PortWatcher> channel,
ClosedCallback closed_callback) {
fbl::AutoLock lock(&lock_);
ZX_DEBUG_ASSERT(!binding_.has_value());
// Gather all existing ports.
for (const netdev::wire::PortId& port_id : existing_ports) {
Event event;
event.SetExisting(port_id);
if (zx_status_t status = QueueEvent(event); status != ZX_OK) {
return status;
}
}
Event idle;
idle.SetIdle();
if (zx_status_t status = QueueEvent(idle); status != ZX_OK) {
return status;
}
binding_ = fidl::BindServer(
dispatcher, std::move(channel), this,
[](PortWatcher* closed_ptr, fidl::UnbindInfo info,
fidl::ServerEnd<netdev::PortWatcher> /*unused*/) {
LOGF_TRACE("port watcher closed: %s", info.FormatDescription().c_str());
PortWatcher& closed = *closed_ptr;
fbl::AutoLock lock(&closed.lock_);
closed.binding_.reset();
std::optional pending_txn = std::exchange(closed.pending_txn_, std::nullopt);
if (pending_txn.has_value()) {
pending_txn.value().Close(ZX_ERR_CANCELED);
}
auto callback = std::exchange(closed.closed_cb_, nullptr);
lock.release();
if (callback) {
callback(closed);
}
});
closed_cb_ = std::move(closed_callback);
return ZX_OK;
}
void PortWatcher::Unbind() {
fbl::AutoLock lock(&lock_);
if (binding_.has_value()) {
binding_->Unbind();
}
}
void PortWatcher::Watch(WatchCompleter::Sync& completer) {
LOGF_TRACE("PortWatcher::%s(_, _)", __FUNCTION__);
fbl::AutoLock lock(&lock_);
if (event_queue_.is_empty()) {
if (pending_txn_.has_value()) {
// Can't enqueue more than one watch call.
completer.Close(ZX_ERR_BAD_STATE);
return;
}
pending_txn_ = completer.ToAsync();
return;
}
std::unique_ptr event = event_queue_.pop_front();
completer.Reply(event->event());
}
zx_status_t PortWatcher::QueueEvent(const PortWatcher::Event& event) {
LOGF_TRACE("PortWatcher::%s(%ld); queue = %ld", __FUNCTION__,
static_cast<long>(event.event().Which()), event_queue_.size());
if (event_queue_.size() == kMaximumQueuedEvents) {
return ZX_ERR_CANCELED;
}
ZX_ASSERT_MSG(event_queue_.size() < kMaximumQueuedEvents, "too many events in queue: %ld > %ld",
event_queue_.size(), kMaximumQueuedEvents);
fbl::AllocChecker ac;
std::unique_ptr queueable = fbl::make_unique_checked<Event>(&ac, event);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
event_queue_.push_back(std::move(queueable));
return ZX_OK;
}
void PortWatcher::PortAdded(netdev::wire::PortId port_id) {
fbl::AutoLock lock(&lock_);
Event event;
event.SetAdded(port_id);
ProcessEvent(event);
}
void PortWatcher::PortRemoved(netdev::wire::PortId port_id) {
fbl::AutoLock lock(&lock_);
Event event;
event.SetRemoved(port_id);
ProcessEvent(event);
}
void PortWatcher::ProcessEvent(const Event& event) {
std::optional txn = std::exchange(pending_txn_, std::nullopt);
if (txn.has_value()) {
txn.value().Reply(event.event());
return;
}
zx_status_t status = QueueEvent(event);
if (status != ZX_OK && binding_.has_value()) {
binding_->Close(status);
}
}
PortWatcher::Event::Event(const PortWatcher::Event& other) {
switch (other.event_.Which()) {
case netdev::wire::DevicePortEvent::Tag::kExisting:
SetExisting(other.port_id_);
break;
case netdev::wire::DevicePortEvent::Tag::kAdded:
SetAdded(other.port_id_);
break;
case netdev::wire::DevicePortEvent::Tag::kRemoved:
SetRemoved(other.port_id_);
break;
case netdev::wire::DevicePortEvent::Tag::kIdle:
SetIdle();
break;
}
}
void PortWatcher::Event::SetExisting(netdev::wire::PortId port_id) {
port_id_ = port_id;
event_ = netdev::wire::DevicePortEvent::WithExisting(port_id_);
}
void PortWatcher::Event::SetAdded(netdev::wire::PortId port_id) {
port_id_ = port_id;
event_ = netdev::wire::DevicePortEvent::WithAdded(port_id_);
}
void PortWatcher::Event::SetRemoved(netdev::wire::PortId port_id) {
port_id_ = port_id;
event_ = netdev::wire::DevicePortEvent::WithRemoved(port_id_);
}
void PortWatcher::Event::SetIdle() { event_ = netdev::wire::DevicePortEvent::WithIdle(empty_); }
} // namespace network::internal