blob: 75b54a83c9f6d465132b3dbb39338676eb15dc10 [file] [log] [blame]
// Copyright 2019 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/overnet/streamlinkfuzzer/cpp/fidl.h>
#include <queue>
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
#include "src/connectivity/overnet/lib/links/stream_link.h"
#include "src/connectivity/overnet/lib/protocol/fidl.h"
#include "src/connectivity/overnet/lib/routing/router.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
using namespace overnet;
namespace {
class FuzzedStreamLink final : public StreamLink {
public:
FuzzedStreamLink(Router* router, NodeId peer)
: StreamLink(router, peer, 64, 1), timer_(router->timer()) {}
bool is_busy() const { return !done_.empty(); }
void Emit(Slice slice, Callback<Status> done) override {
if (!done_.empty()) {
abort();
}
pending_.Append(slice);
done_ = std::move(done);
}
void Done() {
if (!done_.empty()) {
done_(Status::Ok());
}
}
void Flush(uint64_t bytes) {
auto pending_length = pending_.length();
if (bytes == 0) {
} else if (bytes >= pending_length) {
partner_->Process(timer_->Now(), std::move(pending_));
} else {
partner_->Process(timer_->Now(), pending_.TakeUntilOffset(bytes));
}
}
void set_partner(FuzzedStreamLink* partner) { partner_ = partner; }
private:
Timer* const timer_;
Slice pending_;
Callback<Status> done_;
FuzzedStreamLink* partner_ = nullptr;
};
class FuzzedHandler final : public Router::StreamHandler {
public:
~FuzzedHandler() {
if (!expectations_.empty()) {
abort();
}
}
void Expect(Slice slice) { expectations_.emplace(std::move(slice)); }
void RouterClose(Callback<void> quiesced) override {
if (!expectations_.empty()) {
abort();
}
}
void HandleMessage(SeqNum seq, TimeStamp received, Slice data) override {
if (data != expectations_.front()) {
abort();
}
expectations_.pop();
}
private:
std::queue<Slice> expectations_;
};
class StreamLinkFuzzer {
public:
StreamLinkFuzzer(bool log_stuff)
: logging_(log_stuff ? new Logging(&timer_) : nullptr) {
auto link = MakeLink<FuzzedStreamLink>(&router_1_, NodeId(2));
link_12_ = link.get();
router_1_.RegisterLink(std::move(link));
link = MakeLink<FuzzedStreamLink>(&router_2_, NodeId(1));
link_21_ = link.get();
router_2_.RegisterLink(std::move(link));
link_12_->set_partner(link_21_);
link_21_->set_partner(link_12_);
router_2_.RegisterStream(NodeId(1), StreamId(1), &handler_1_).MustSucceed();
router_1_.RegisterStream(NodeId(2), StreamId(1), &handler_2_).MustSucceed();
}
~StreamLinkFuzzer() {
link_12_->Flush(std::numeric_limits<size_t>::max());
link_21_->Flush(std::numeric_limits<size_t>::max());
link_12_->Done();
link_21_->Done();
router_2_.UnregisterStream(NodeId(1), StreamId(1), &handler_1_)
.MustSucceed();
router_1_.UnregisterStream(NodeId(2), StreamId(1), &handler_2_)
.MustSucceed();
int waiting = 2;
router_1_.Close([&] { waiting--; });
router_2_.Close([&] { waiting--; });
while (waiting) {
timer_.StepUntilNextEvent();
}
}
void Run(fuchsia::overnet::streamlinkfuzzer::PeerToPeerPlan plan) {
using namespace fuchsia::overnet::streamlinkfuzzer;
for (const auto& action : plan.actions) {
if (!valid_node(action.node)) {
continue;
}
switch (action.type.Which()) {
case PeerToPeerActionType::Tag::Empty:
break;
case PeerToPeerActionType::Tag::kSendPacket: {
auto* lnk = link(action.node);
auto packet = Slice::FromContainer(action.type.send_packet());
if (!lnk->is_busy()) {
handler(action.node)->Expect(packet);
}
auto cur_seq = seq_;
lnk->Forward(Message::SimpleForwarder(
std::move(RoutableMessage(src(action.node))
.AddDestination(dst(action.node), StreamId(1),
SeqNum(seq_++, cur_seq))),
std::move(packet), timer_.Now()));
} break;
case PeerToPeerActionType::Tag::kSentPacket:
link(action.node)->Done();
break;
case PeerToPeerActionType::Tag::kAllowBytes:
link(action.node)->Flush(action.type.allow_bytes());
break;
}
timer_.Step(1);
}
}
private:
bool valid_node(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return true;
default:
return false;
}
}
FuzzedStreamLink* link(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
return link_12_;
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return link_21_;
}
}
FuzzedHandler* handler(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
return &handler_1_;
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return &handler_2_;
}
}
Router* router(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
return &router_1_;
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return &router_2_;
}
}
NodeId src(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
return NodeId(1);
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return NodeId(2);
}
}
NodeId dst(fuchsia::overnet::streamlinkfuzzer::NodeId id) {
switch (id) {
case fuchsia::overnet::streamlinkfuzzer::NodeId::A:
return NodeId(2);
case fuchsia::overnet::streamlinkfuzzer::NodeId::B:
return NodeId(1);
}
}
TestTimer timer_;
struct Logging {
Logging(Timer* timer) : tracer(timer) {}
TraceCout tracer;
ScopedRenderer set_tracer{&tracer};
};
std::unique_ptr<Logging> logging_;
Router router_1_{&timer_, NodeId(1), false};
Router router_2_{&timer_, NodeId(2), false};
FuzzedStreamLink* link_12_;
FuzzedStreamLink* link_21_;
uint64_t seq_ = 1;
FuzzedHandler handler_1_;
FuzzedHandler handler_2_;
};
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (auto buffer = Decode<fuchsia::overnet::streamlinkfuzzer::PeerToPeerPlan>(
Slice::FromCopiedBuffer(data, size));
buffer.is_ok()) {
StreamLinkFuzzer(false).Run(std::move(*buffer));
}
return 0;
}