blob: 8d0b062e4e9c0752a758882fb8c36253141a9fce [file] [log] [blame] [edit]
// 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.
#ifndef SRC_SYS_FUZZING_COMMON_BINDING_H_
#define SRC_SYS_FUZZING_COMMON_BINDING_H_
#include <lib/async/cpp/task.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/sync/completion.h>
#include <lib/syslog/cpp/macros.h>
#include <threads.h>
#include <zircon/status.h>
#include <mutex>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/sys/fuzzing/common/dispatcher.h"
#include "src/sys/fuzzing/common/signal-coordinator.h"
namespace fuzzing {
// This class wraps |fidl::Binding| in an object that can be created and destroyed on both
// dispatcher and non-dispatcher threads, allowing for easier RAII-like semantics.
template <typename T>
class Binding {
public:
Binding(T* t, std::shared_ptr<Dispatcher> dispatcher)
: binding_(t), dispatcher_(std::move(dispatcher)) {
FX_CHECK(dispatcher_);
}
// The HLCPP bindings are thread-hostile. In particular, they can only be safely unbound from the
// dispatcher thread, or run the risk of racing being unbound by a peer closure.
virtual ~Binding() { Unbind(); }
const std::shared_ptr<Dispatcher>& dispatcher() const { return dispatcher_; }
bool is_bound() const { return binding_.is_bound(); }
// Creates a channel, binds it to this object, and returns the other end in an InterfaceHandle.
// This can be called from a non-dispatch thread. Does nothing if already bound.
fidl::InterfaceHandle<T> NewBinding() FXL_LOCKS_EXCLUDED(mutex_) {
fidl::InterfaceHandle<T> client;
Bind(client.NewRequest());
return client;
}
// Binds the FIDL interface request to this object. This can be called from a non-dispatch thread.
zx_status_t Bind(fidl::InterfaceRequest<T> request) FXL_LOCKS_EXCLUDED(mutex_) {
return Bind(request.TakeChannel());
}
// Binds the channel to this object. This can be called from a non-dispatch thread. If this object
// is currently bound, it will first unbind. Returns immediately with |ZX_ERR_BAD_STATE| if a call
// to |Bind| and |Unbind| is outstanding.
zx_status_t Bind(zx::channel channel) FXL_LOCKS_EXCLUDED(mutex_) {
Unbind();
bool update_posted = false;
{
std::lock_guard<std::mutex> lock(mutex_);
switch (state_) {
case kUnbound:
channel_ = std::move(channel);
state_ = kBinding;
update_posted = MaybePostUpdate();
break;
default:
return ZX_ERR_BAD_STATE;
}
}
return AwaitUpdate(update_posted);
}
// Unbinds (and closes) the underlying channel from this object. This can be called from a
// non-dispatch thread. Does nothing if not bound. Calling |Unbind| while a call to |Bind| is
// outstanding effectively cancels the latter.
void Unbind() FXL_LOCKS_EXCLUDED(mutex_) {
bool update_posted = false;
{
std::lock_guard<std::mutex> lock(mutex_);
switch (state_) {
case kUnbound:
return;
case kBinding:
// Update already queued; change what it will do.
state_ = kUnbinding;
break;
case kBound:
state_ = kUnbinding;
update_posted = MaybePostUpdate();
break;
case kUnbinding:
// Update already queued.
break;
}
}
AwaitUpdate(update_posted);
}
private:
// Attempts to post a call to |Update| on the dispatcher thread. Returns false if this is being
// called on the dispatcher thread or if the dispatcher is shutting down; otherwise returns true.
// The return value should be passed to |AwaitUpdate|.
bool MaybePostUpdate() FXL_REQUIRE(mutex_) {
if (thrd_equal(dispatcher_->thrd(), thrd_current())) {
return false;
}
sync_completion_reset(&sync_);
auto status = async::PostTask(dispatcher_->get(), [this]() {
Update();
sync_completion_signal(&sync_);
});
return status == ZX_OK;
}
// Binds or unbinds the channel based on the current state. This should be called on the
// dispatcher thread only to avoid racing message arrival.
void Update() FXL_LOCKS_EXCLUDED(mutex_) {
std::lock_guard<std::mutex> lock(mutex_);
switch (state_) {
case kBinding:
result_ = binding_.Bind(std::move(channel_), dispatcher_->get());
if (result_ != ZX_OK) {
state_ = kUnbinding;
}
break;
case kUnbinding:
if (binding_.is_bound()) {
binding_.Unbind();
}
result_ = ZX_OK;
break;
case kUnbound:
case kBound:
FX_NOTREACHED();
}
}
// Ensures |Update| has been called before returning its result. If |update_posted| is false, it
// calls |Update| directly; otherwise it waits until the task posted by |MaybePostUpdate| has
// completed.
zx_status_t AwaitUpdate(bool update_posted) FXL_LOCKS_EXCLUDED(mutex_) {
if (!update_posted) {
Update();
} else {
sync_completion_wait(&sync_, ZX_TIME_INFINITE);
}
std::lock_guard<std::mutex> lock(mutex_);
switch (state_) {
case kBinding:
state_ = kBound;
break;
case kUnbinding:
state_ = kUnbound;
break;
case kUnbound:
case kBound:
FX_NOTREACHED();
}
channel_.reset();
return result_;
}
fidl::Binding<T> binding_;
std::shared_ptr<Dispatcher> dispatcher_;
std::mutex mutex_;
enum : uint8_t {
kUnbound,
kBinding,
kBound,
kUnbinding,
} state_ FXL_GUARDED_BY(mutex_) = kUnbound;
zx::channel channel_ FXL_GUARDED_BY(mutex_);
zx_status_t result_ FXL_GUARDED_BY(mutex_) = ZX_OK;
sync_completion_t sync_;
};
} // namespace fuzzing
#endif // SRC_SYS_FUZZING_COMMON_BINDING_H_