blob: c1f7b5301040717a8254eb4b2cdc4fd0dd63a61e [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.
// This fuzzer tests the hypothesis that the packet nub protocol allows
// eventual connectivity even in the face of packet loss.
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
#include "src/connectivity/overnet/lib/links/packet_nub.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
using namespace overnet;
///////////////////////////////////////////////////////////////////////////////
// Simulation
class Fuzzer;
class Nub final : public PacketNub<int, 256> {
public:
Nub(Fuzzer* fuzzer, uint8_t index);
void SendTo(int dest, Slice slice) override;
Router* GetRouter() override;
void Publish(LinkPtr<> link) override {
router_.RegisterLink(std::move(link));
}
private:
Fuzzer* const fuzzer_;
const uint8_t index_;
Router router_;
};
class Fuzzer {
public:
Fuzzer();
Timer* timer() { return &timer_; }
bool IsDone() {
return nub1_.GetRouter()->HasRouteTo(NodeId(2)) &&
nub2_.GetRouter()->HasRouteTo(NodeId(1));
}
bool StepTime() { return timer_.StepUntilNextEvent(); }
bool StepTime(uint64_t us) { return timer_.Step(us); }
void QueueSend(int src, int dest, Slice slice);
bool FlushPackets();
void AllowPacket(uint64_t packet);
void DropPacket(uint64_t packet);
void Initiate1();
void Initiate2();
private:
TestTimer timer_;
TraceCout cout_{&timer_};
// ScopedRenderer renderer_{&cout_};
Nub nub1_{this, 1};
Nub nub2_{this, 2};
enum class PacketState : uint8_t {
QUEUED,
SENT,
DROPPED,
};
struct Packet {
int src;
int dest;
PacketState state;
Slice payload;
};
std::vector<Packet> packets_;
};
Nub::Nub(Fuzzer* fuzzer, uint8_t index)
: PacketNub(fuzzer->timer(), NodeId(index)),
fuzzer_(fuzzer),
index_(index),
router_(fuzzer->timer(), NodeId(index), false) {}
void Nub::SendTo(int dest, Slice slice) {
fuzzer_->QueueSend(index_, dest, std::move(slice));
}
Router* Nub::GetRouter() { return &router_; }
Fuzzer::Fuzzer() = default;
void Fuzzer::Initiate1() { nub1_.Initiate({2}, NodeId(2)); }
void Fuzzer::Initiate2() { nub2_.Initiate({1}, NodeId(1)); }
void Fuzzer::QueueSend(int src, int dest, Slice slice) {
packets_.emplace_back(
Packet{src, dest, PacketState::QUEUED, std::move(slice)});
}
bool Fuzzer::FlushPackets() {
bool flushed_any = false;
for (size_t i = 0; i < packets_.size(); i++) {
if (packets_[i].state == PacketState::QUEUED) {
AllowPacket(i);
assert(packets_[i].state != PacketState::QUEUED);
flushed_any = true;
}
}
return flushed_any;
}
void Fuzzer::AllowPacket(uint64_t packet) {
if (packet >= packets_.size()) {
return;
}
auto& p = packets_[packet];
if (p.state == PacketState::DROPPED) {
return;
}
p.state = PacketState::SENT;
const auto now = timer_.Now();
switch (p.dest) {
case 1:
nub1_.Process(now, p.src, p.payload);
break;
case 2:
nub2_.Process(now, p.src, p.payload);
break;
}
}
void Fuzzer::DropPacket(uint64_t packet) {
if (packet >= packets_.size()) {
return;
}
auto& p = packets_[packet];
p.state = PacketState::DROPPED;
}
///////////////////////////////////////////////////////////////////////////////
// Input
class InputStream {
public:
InputStream(const uint8_t* data, size_t size)
: cur_(data), end_(data + size) {}
uint64_t Next64() {
uint64_t out;
if (!varint::Read(&cur_, end_, &out))
out = 0;
return out;
}
uint8_t NextByte() {
if (cur_ == end_)
return 0;
return *cur_++;
}
bool IsEof() const { return cur_ == end_; }
private:
const uint8_t* cur_;
const uint8_t* end_;
};
///////////////////////////////////////////////////////////////////////////////
// Main loop
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
InputStream input(data, size);
Fuzzer fuzzer;
const uint8_t end_behavior = input.NextByte();
while (!fuzzer.IsDone()) {
switch (input.NextByte()) {
case 0:
OVERNET_TRACE(INFO) << "Fuzzer: Flush";
do {
if (input.IsEof()) {
if (end_behavior & 1) {
OVERNET_TRACE(INFO) << "Fuzzer: Initiate from 1 -> 2";
fuzzer.Initiate1();
} else {
OVERNET_TRACE(INFO) << "Fuzzer: Initiate from 2 -> 1";
fuzzer.Initiate2();
}
}
} while (fuzzer.FlushPackets() || fuzzer.StepTime());
if (input.IsEof() && !fuzzer.IsDone()) {
std::cerr << "Failed to connect\n";
abort();
}
break;
case 1: {
auto dt = TimeDelta::FromMicroseconds(input.Next64());
dt = std::min(dt, TimeDelta::FromHours(1));
dt = std::max(dt, TimeDelta::FromMicroseconds(1));
OVERNET_TRACE(INFO) << "Fuzzer: Step " << dt;
fuzzer.StepTime(dt.as_us());
} break;
case 2: {
auto idx = input.Next64();
OVERNET_TRACE(INFO) << "Fuzzer: Allow packet " << idx;
fuzzer.AllowPacket(idx);
} break;
case 3: {
auto idx = input.Next64();
OVERNET_TRACE(INFO) << "Fuzzer: Drop packet " << idx;
fuzzer.DropPacket(idx);
} break;
case 4: {
OVERNET_TRACE(INFO) << "Fuzzer: Initiate from 1 -> 2";
fuzzer.Initiate1();
} break;
case 5: {
OVERNET_TRACE(INFO) << "Fuzzer: Initiate from 2 -> 1";
fuzzer.Initiate2();
} break;
default: {
return 0;
}
}
}
return 0;
}