blob: ba6d2089a80a76f36ecf019eb42ef00d03545f25 [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/protocol/reliable_framer.h"
#include "src/connectivity/overnet/lib/protocol/unreliable_framer.h"
#include "src/connectivity/overnet/lib/routing/router.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
using namespace overnet;
namespace {
const auto kDummyMutation = [](auto, auto) -> Slice { return Slice(); };
class StreamMutator {
public:
StreamMutator(std::vector<fuchsia::overnet::streamlinkfuzzer::StreamMutation>
mutations) {
for (const auto& mut : mutations) {
switch (mut.Which()) {
case fuchsia::overnet::streamlinkfuzzer::StreamMutation::Tag::kFlipBit:
mops_.emplace_back(
mut.flip_bit() / 8,
[bit = 1 << (mut.flip_bit() % 8)](uint8_t offset, Slice slice) {
return slice.MutateUnique(
[offset, bit](uint8_t* p) { p[offset] ^= bit; });
});
break;
case fuchsia::overnet::streamlinkfuzzer::StreamMutation::Tag::Empty:
break;
}
}
std::stable_sort(mops_.begin(), mops_.end(), CompareMOpPos);
}
Slice Mutate(uint64_t offset, Slice incoming) {
for (auto it = std::lower_bound(mops_.begin(), mops_.end(),
MOp(offset, kDummyMutation), CompareMOpPos);
it != mops_.end() && it->first < offset + incoming.length(); ++it) {
incoming = it->second(it->first - offset, incoming);
}
return incoming;
}
private:
using MOp = std::pair<uint64_t, std::function<Slice(uint64_t, Slice)>>;
std::vector<MOp> mops_;
static bool CompareMOpPos(const MOp& a, const MOp& b) {
return a.first < b.first;
}
};
class FuzzedStreamLink final : public StreamLink {
public:
FuzzedStreamLink(Router* router, NodeId peer,
std::unique_ptr<StreamFramer> framer, StreamMutator mutator)
: StreamLink(router, peer, std::move(framer), 1),
timer_(router->timer()),
mutator_(std::move(mutator)) {}
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) {
return;
}
Slice process = mutator_.Mutate(
offset_, bytes >= pending_length ? std::move(pending_)
: pending_.TakeUntilOffset(bytes));
offset_ += process.length();
partner_->Process(timer_->Now(), std::move(process));
}
void set_partner(FuzzedStreamLink* partner) { partner_ = partner; }
private:
Timer* const timer_;
Slice pending_;
Callback<Status> done_;
FuzzedStreamLink* partner_ = nullptr;
uint64_t offset_ = 0;
StreamMutator mutator_;
};
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, std::unique_ptr<StreamFramer> framer,
StreamMutator mut_1_to_2, StreamMutator mut_2_to_1)
: logging_(log_stuff ? new Logging(&timer_) : nullptr) {
auto link = MakeLink<FuzzedStreamLink>(
&router_1_, NodeId(2), std::move(framer), std::move(mut_1_to_2));
link_12_ = link.get();
router_1_.RegisterLink(std::move(link));
link = MakeLink<FuzzedStreamLink>(&router_2_, NodeId(1), std::move(framer),
std::move(mut_2_to_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_;
};
struct Helpers {
std::unique_ptr<StreamFramer> framer;
StreamMutator mut_1_to_2;
StreamMutator mut_2_to_1;
};
Helpers MakeHelpers(
fuchsia::overnet::streamlinkfuzzer::PeerToPeerLinkDescription* desc) {
switch (desc->Which()) {
case fuchsia::overnet::streamlinkfuzzer::PeerToPeerLinkDescription::Tag::
Empty:
return Helpers{nullptr, StreamMutator({}), StreamMutator({})};
case fuchsia::overnet::streamlinkfuzzer::PeerToPeerLinkDescription::Tag::
kReliable:
return Helpers{std::make_unique<ReliableFramer>(), StreamMutator({}),
StreamMutator({})};
case fuchsia::overnet::streamlinkfuzzer::PeerToPeerLinkDescription::Tag::
kUnreliable:
return Helpers{
std::make_unique<UnreliableFramer>(),
StreamMutator(std::move(desc->unreliable().mutation_plan_1_to_2)),
StreamMutator(std::move(desc->unreliable().mutation_plan_2_to_1))};
}
}
} // 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()) {
if (auto helpers = MakeHelpers(&buffer->link_description); helpers.framer) {
StreamLinkFuzzer(false, std::move(helpers.framer),
std::move(helpers.mut_1_to_2),
std::move(helpers.mut_2_to_1))
.Run(std::move(*buffer));
}
}
return 0;
}