[overnet] Stream links, utilization fixes
Change-Id: Idcf5f82b1dbf71f255646a2b8514b95dac456fe8
diff --git a/garnet/packages/prod/BUILD.gn b/garnet/packages/prod/BUILD.gn
index 07ad31cb..11963f3 100644
--- a/garnet/packages/prod/BUILD.gn
+++ b/garnet/packages/prod/BUILD.gn
@@ -385,6 +385,7 @@
testonly = true
public_deps = [
"//src/connectivity/overnet/overnetstack",
+ "//src/connectivity/overnet/tools/ascendd",
"//src/connectivity/overnet/tools/onet",
]
}
diff --git a/sdk/fidl/fuchsia.overnet.protocol/BUILD.gn b/sdk/fidl/fuchsia.overnet.protocol/BUILD.gn
index f2a1031..e64a99c 100644
--- a/sdk/fidl/fuchsia.overnet.protocol/BUILD.gn
+++ b/sdk/fidl/fuchsia.overnet.protocol/BUILD.gn
@@ -9,6 +9,7 @@
"labels.fidl",
"peer_protocol.fidl",
"routing.fidl",
+ "stream_socket.fidl",
"zircon_proxy.fidl",
]
}
diff --git a/sdk/fidl/fuchsia.overnet.protocol/labels.fidl b/sdk/fidl/fuchsia.overnet.protocol/labels.fidl
index 015976a..badab0a 100644
--- a/sdk/fidl/fuchsia.overnet.protocol/labels.fidl
+++ b/sdk/fidl/fuchsia.overnet.protocol/labels.fidl
@@ -14,6 +14,9 @@
uint64 id;
};
+/// Node-local link label
+using LinkId = uint64;
+
/// Reliability and ordering constraints for a stream.
enum ReliabilityAndOrdering {
/// Datagrams are delivered reliably in an ordered fashion.
diff --git a/sdk/fidl/fuchsia.overnet.protocol/routing.fidl b/sdk/fidl/fuchsia.overnet.protocol/routing.fidl
index dc588a8..f14eaec 100644
--- a/sdk/fidl/fuchsia.overnet.protocol/routing.fidl
+++ b/sdk/fidl/fuchsia.overnet.protocol/routing.fidl
@@ -37,7 +37,7 @@
/// An identifier (chosen by node `from`) to label this link.
/// `from` must guarantee that the tuple (from, to, local_id) is unique
/// for each of it's held links.
- uint64 local_id;
+ LinkId local_id;
/// A monotonically increasing version counter for this links status.
uint64 version;
diff --git a/sdk/fidl/fuchsia.overnet.protocol/stream_socket.fidl b/sdk/fidl/fuchsia.overnet.protocol/stream_socket.fidl
new file mode 100644
index 0000000..37984f0
--- /dev/null
+++ b/sdk/fidl/fuchsia.overnet.protocol/stream_socket.fidl
@@ -0,0 +1,15 @@
+// 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.
+
+library fuchsia.overnet.protocol;
+
+// Introduction packet sent on stream oriented links between Overnet nodes
+table StreamSocketGreeting {
+ // Protocol identification string; different kinds of streams might choose a different value here
+ 1: string:32 magic_string;
+ // Overnet NodeId of the sender
+ 2: NodeId node_id;
+ // Sender's LinkId for this link
+ 3: LinkId local_link_id;
+};
diff --git a/src/connectivity/overnet/lib/BUILD.gn b/src/connectivity/overnet/lib/BUILD.gn
index e645b12..203336e9 100644
--- a/src/connectivity/overnet/lib/BUILD.gn
+++ b/src/connectivity/overnet/lib/BUILD.gn
@@ -18,7 +18,6 @@
"packet_protocol:lib",
"protocol:lib",
"routing:lib",
- "stats:lib",
"vocabulary:lib",
]
}
@@ -26,6 +25,7 @@
test("overnet_unittests") {
deps = [
"datagram_stream:tests",
+ "embedded:tests",
"endpoint:tests",
"environment:tests",
"labels:tests",
diff --git a/src/connectivity/overnet/lib/datagram_stream/BUILD.gn b/src/connectivity/overnet/lib/datagram_stream/BUILD.gn
index dd0e301..3325422 100644
--- a/src/connectivity/overnet/lib/datagram_stream/BUILD.gn
+++ b/src/connectivity/overnet/lib/datagram_stream/BUILD.gn
@@ -27,13 +27,14 @@
"datagram_stream.cc",
"datagram_stream.h",
]
- deps = [
+ public_deps = [
":linearizer",
":receive_mode",
"//sdk/fidl/fuchsia.overnet.protocol",
"//src/connectivity/overnet/lib/environment:timer",
"//src/connectivity/overnet/lib/environment:trace",
"//src/connectivity/overnet/lib/labels:seq_num",
+ "//src/connectivity/overnet/lib/stats:stream",
"//src/connectivity/overnet/lib/packet_protocol",
"//src/connectivity/overnet/lib/routing:router",
"//src/connectivity/overnet/lib/vocabulary:internal_list",
@@ -65,6 +66,7 @@
]
deps = [
"//src/connectivity/overnet/lib/environment:trace",
+ "//src/connectivity/overnet/lib/stats:stream",
"//src/connectivity/overnet/lib/vocabulary:callback",
"//src/connectivity/overnet/lib/vocabulary:optional",
"//src/connectivity/overnet/lib/vocabulary:slice",
diff --git a/src/connectivity/overnet/lib/datagram_stream/datagram_stream.cc b/src/connectivity/overnet/lib/datagram_stream/datagram_stream.cc
index 7078437..ee54605 100644
--- a/src/connectivity/overnet/lib/datagram_stream/datagram_stream.cc
+++ b/src/connectivity/overnet/lib/datagram_stream/datagram_stream.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/datagram_stream/datagram_stream.h"
+
#include <sstream>
namespace overnet {
@@ -128,7 +129,7 @@
// TODO(ctiller): What should mss be? Hardcoding to 2048 for now.
packet_protocol_(
timer_, [router] { return (*router->rng())(); }, this,
- PacketProtocol::NullCodec(), 2048) {}
+ PacketProtocol::NullCodec(), 2048, true) {}
void DatagramStream::Register() {
ScopedModule<DatagramStream> scoped_module(this);
@@ -574,6 +575,7 @@
}
push_offset_ += chunk_length;
Chunk chunk{chunk_start, end_byte == payload_length_, std::move(item)};
+ stream()->stream_stats_.send_chunk_push++;
stream()->SendChunk(*this, std::move(chunk), std::move(started));
}
@@ -742,6 +744,7 @@
<< " message_id_length=" << (int)message_id_length;
if (args->max_length <=
message_id_length + varint::WireSizeFor(pending_send.chunk.offset)) {
+ stream->stream_stats_.send_chunk_cancel_packet_too_small++;
stream->SendChunk(pending_send.state, std::move(pending_send.chunk),
std::move(cb));
return ChunkAndOptState{Nothing, pending_send.state};
@@ -751,12 +754,16 @@
varint::WireSizeFor(pending_send.chunk.offset));
uint64_t take_len =
varint::MaximumLengthWithPrefix(args->max_length - message_id_length);
- OVERNET_TRACE(DEBUG) << "TAKE " << take_len;
+ OVERNET_TRACE(DEBUG) << "TAKE " << take_len << " from "
+ << pending_send.chunk.slice.length();
if (take_len < pending_send.chunk.slice.length()) {
+ stream->stream_stats_.send_chunk_split_packet_too_small++;
Chunk first = pending_send.chunk.TakeUntilSliceOffset(take_len);
stream->SendChunk(pending_send.state, std::move(pending_send.chunk),
Callback<void>::Ignored());
pending_send.chunk = std::move(first);
+ } else {
+ stream->stream_stats_.send_chunk_take_entire_chunk++;
}
return ChunkAndOptState{pending_send.chunk, pending_send.state};
}
@@ -874,6 +881,7 @@
if (status.code() == StatusCode::UNAVAILABLE &&
!state.stream()->IsClosedForSending()) {
// Send failed, still open, and retryable: retry.
+ stream_stats_.send_chunk_nacked++;
SendChunk(std::move(state), std::move(chunk), Callback<void>::Ignored());
}
}
diff --git a/src/connectivity/overnet/lib/datagram_stream/datagram_stream.h b/src/connectivity/overnet/lib/datagram_stream/datagram_stream.h
index eb37630..97faf9a 100644
--- a/src/connectivity/overnet/lib/datagram_stream/datagram_stream.h
+++ b/src/connectivity/overnet/lib/datagram_stream/datagram_stream.h
@@ -4,7 +4,6 @@
#pragma once
-#include <queue> // TODO(ctiller): switch to a short queue (inlined 1-2 elems, linked list)
#include "src/connectivity/overnet/lib/datagram_stream/linearizer.h"
#include "src/connectivity/overnet/lib/datagram_stream/receive_mode.h"
#include "src/connectivity/overnet/lib/environment/timer.h"
@@ -12,6 +11,7 @@
#include "src/connectivity/overnet/lib/labels/seq_num.h"
#include "src/connectivity/overnet/lib/packet_protocol/packet_protocol.h"
#include "src/connectivity/overnet/lib/routing/router.h"
+#include "src/connectivity/overnet/lib/stats/stream.h"
#include "src/connectivity/overnet/lib/vocabulary/internal_list.h"
#include "src/connectivity/overnet/lib/vocabulary/slice.h"
@@ -137,7 +137,12 @@
Module::DATAGRAM_STREAM_INCOMING_MESSAGE;
IncomingMessage(DatagramStream* stream, uint64_t msg_id)
- : linearizer_(2 * stream->packet_protocol_.maximum_send_size()),
+ : protocol_(&stream->packet_protocol_),
+ // TODO(ctiller): What should the bound be here? 4*mss is a guess,
+ // nothing more.
+ linearizer_(std::max(uint64_t(4 * protocol_->maximum_send_size()),
+ protocol_->bdp_estimate()),
+ &stream->stream_stats_),
msg_id_(msg_id) {}
void Pull(StatusOrCallback<Optional<Slice>>&& done) {
@@ -152,6 +157,7 @@
[[nodiscard]] bool Push(Chunk&& chunk) {
ScopedModule<IncomingMessage> in_im(this);
+ linearizer_.UpdateMaxBuffer(protocol_->bdp_estimate());
return linearizer_.Push(std::forward<Chunk>(chunk));
}
@@ -167,6 +173,7 @@
InternalListNode<IncomingMessage> incoming_link;
private:
+ PacketProtocol* const protocol_;
Linearizer linearizer_;
const uint64_t msg_id_;
};
@@ -341,6 +348,9 @@
NodeId peer() const { return peer_; }
+ const LinkStats* link_stats() const { return packet_protocol_.stats(); }
+ const StreamStats* stream_stats() const { return &stream_stats_; }
+
protected:
// Must be called by derived classes, after construction and before any other
// methods.
@@ -376,6 +386,7 @@
uint64_t largest_incoming_message_id_seen_ = 0;
receive_mode::ParameterizedReceiveMode receive_mode_;
PacketProtocol packet_protocol_;
+ StreamStats stream_stats_;
StreamRef close_ref_{this}; // Keep stream alive until closed
enum class CloseState : uint8_t {
diff --git a/src/connectivity/overnet/lib/datagram_stream/linearizer.cc b/src/connectivity/overnet/lib/datagram_stream/linearizer.cc
index 1812919..6275edd 100644
--- a/src/connectivity/overnet/lib/datagram_stream/linearizer.cc
+++ b/src/connectivity/overnet/lib/datagram_stream/linearizer.cc
@@ -13,7 +13,8 @@
namespace overnet {
-Linearizer::Linearizer(uint64_t max_buffer) : max_buffer_(max_buffer) {}
+Linearizer::Linearizer(uint64_t max_buffer, StreamStats* stats)
+ : max_buffer_(max_buffer), stats_(stats) {}
Linearizer::~Linearizer() {
switch (read_mode_) {
@@ -140,6 +141,7 @@
if (chunk_start > offset_ && chunk_end > offset_ + max_buffer_) {
OVERNET_TRACE(DEBUG) << "Push reject: past end of buffering window;"
<< " max_buffer=" << max_buffer_;
+ stats_->linearizer_reject_past_end_of_buffering++;
return false;
}
@@ -180,6 +182,7 @@
}
if (chunk_end == chunk_start) {
+ stats_->linearizer_empty_chunk++;
return true;
}
@@ -188,6 +191,7 @@
if (read_mode_ == ReadMode::ReadSlice && chunk_start == offset_ &&
(pending_push_.empty() || pending_push_.begin()->first > chunk_end)) {
OVERNET_TRACE(DEBUG) << "Push: fast-path";
+ stats_->linearizer_fast_path_taken++;
offset_ += chunk.slice.length();
auto push = std::move(ReadSliceToIdle().done);
if (length_) {
@@ -206,10 +210,12 @@
if (chunk_start < offset_) {
if (chunk_end > offset_) {
OVERNET_TRACE(DEBUG) << "Push: trim begin";
+ stats_->linearizer_partial_ignore_begin++;
chunk.TrimBegin(offset_ - chunk_start);
chunk_start = chunk.offset;
} else {
OVERNET_TRACE(DEBUG) << "Push: all prior";
+ stats_->linearizer_ignore_all_prior++;
return true;
}
}
@@ -220,8 +226,10 @@
// exit conditions, and we've got some common checks to do once it's finished.
if (pending_push_.empty()) {
OVERNET_TRACE(DEBUG) << "Push: first pending";
+ stats_->linearizer_new_pending_queue++;
pending_push_.emplace(chunk.offset, std::move(chunk.slice));
} else {
+ stats_->linearizer_integrations++;
IntegratePush(std::move(chunk));
}
@@ -256,15 +264,18 @@
OVERNET_TRACE(DEBUG) << "coincident with existing; common_length="
<< common_length;
if (0 != memcmp(chunk.slice.begin(), lb->second.begin(), common_length)) {
+ stats_->linearizer_integration_errors++;
Close(Status(StatusCode::DATA_LOSS,
"Linearizer received different bytes for the same span"))
.Ignore();
} else if (chunk.slice.length() <= lb->second.length()) {
// New chunk is shorter than what's there (or the same length): We're
// done.
+ stats_->linearizer_integration_coincident_shorter++;
} else {
// New chunk is bigger than what's there: we create a new (tail) chunk and
// continue integration
+ stats_->linearizer_integration_coincident_longer++;
chunk.TrimBegin(lb->second.length());
IntegratePush(std::move(chunk));
}
@@ -289,13 +300,16 @@
<< common_length;
if (0 != memcmp(before->second.begin() + (chunk.offset - before->first),
chunk.slice.begin(), common_length)) {
+ stats_->linearizer_integration_errors++;
Close(Status(StatusCode::DATA_LOSS,
"Linearizer received different bytes for the same span"))
.Ignore();
} else if (before_end >= chunk.offset + chunk.slice.length()) {
// New chunk is a subset of the one before: we're done.
+ stats_->linearizer_integration_prior_longer++;
} else {
// Trim the new chunk and continue integration.
+ stats_->linearizer_integration_prior_partial++;
chunk.TrimBegin(before_end - chunk.offset);
IntegratePush(std::move(chunk));
}
@@ -320,6 +334,7 @@
if (0 != memcmp(after->second.begin(),
chunk.slice.begin() + (after->first - chunk.offset),
common_length)) {
+ stats_->linearizer_integration_errors++;
Close(Status(StatusCode::DATA_LOSS,
"Linearizer received different bytes for the same span"))
.Ignore();
@@ -327,6 +342,7 @@
} else if (after->first + after->second.length() <
chunk.offset + chunk.slice.length()) {
OVERNET_TRACE(DEBUG) << "Split and integrate separately";
+ stats_->linearizer_integration_subsequent_splits++;
// Split chunk into two and integrate each separately
Chunk tail = chunk;
chunk.TrimEnd(chunk.offset + chunk.slice.length() - after->first);
@@ -336,6 +352,7 @@
return;
} else {
// Trim so the new chunk no longer overlaps.
+ stats_->linearizer_integration_subsequent_covers++;
chunk.TrimEnd(chunk.offset + chunk.slice.length() - after->first);
}
}
@@ -344,6 +361,7 @@
// We now have a non-overlapping chunk that we can insert.
OVERNET_TRACE(DEBUG) << "add pending start=" << chunk.offset
<< " end=" << (chunk.offset + chunk.slice.length());
+ stats_->linearizer_integration_inserts++;
pending_push_.emplace_hint(lb, chunk.offset, std::move(chunk.slice));
}
diff --git a/src/connectivity/overnet/lib/datagram_stream/linearizer.h b/src/connectivity/overnet/lib/datagram_stream/linearizer.h
index b79cdfe..8d30cd3 100644
--- a/src/connectivity/overnet/lib/datagram_stream/linearizer.h
+++ b/src/connectivity/overnet/lib/datagram_stream/linearizer.h
@@ -5,7 +5,9 @@
#pragma once
#include <map>
+
#include "src/connectivity/overnet/lib/environment/trace.h"
+#include "src/connectivity/overnet/lib/stats/stream.h"
#include "src/connectivity/overnet/lib/vocabulary/callback.h"
#include "src/connectivity/overnet/lib/vocabulary/optional.h"
#include "src/connectivity/overnet/lib/vocabulary/slice.h"
@@ -15,7 +17,7 @@
class Linearizer final {
public:
- explicit Linearizer(uint64_t max_buffer);
+ explicit Linearizer(uint64_t max_buffer, StreamStats* stats);
~Linearizer();
// Input interface.
@@ -24,6 +26,10 @@
// Returns true if successful, false on failure.
[[nodiscard]] bool Push(Chunk chunk);
+ void UpdateMaxBuffer(uint64_t new_max_buffer) {
+ max_buffer_ = std::max(max_buffer_, new_max_buffer);
+ }
+
// Output interface.
void Pull(StatusOrCallback<Optional<Slice>> ready);
void PullAll(StatusOrCallback<Optional<std::vector<Slice>>> ready);
@@ -82,7 +88,7 @@
};
#endif
- const uint64_t max_buffer_;
+ uint64_t max_buffer_;
uint64_t offset_ = 0;
Optional<uint64_t> length_;
std::map<uint64_t, Slice> pending_push_;
@@ -128,6 +134,7 @@
ReadMode read_mode_ = ReadMode::Idle;
ReadData read_data_;
+ StreamStats* const stats_;
void IdleToClosed(const Status& status);
void IdleToReadSlice(StatusOrCallback<Optional<Slice>> done);
diff --git a/src/connectivity/overnet/lib/datagram_stream/linearizer_fuzzer.h b/src/connectivity/overnet/lib/datagram_stream/linearizer_fuzzer.h
index 6bc38f4a..a1d1190 100644
--- a/src/connectivity/overnet/lib/datagram_stream/linearizer_fuzzer.h
+++ b/src/connectivity/overnet/lib/datagram_stream/linearizer_fuzzer.h
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include <vector>
+
#include "src/connectivity/overnet/lib/datagram_stream/linearizer.h"
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
@@ -62,7 +63,8 @@
StatusCode closed_status_;
bool waiting_for_pull_ = false;
- Linearizer linearizer_{kBuffer};
+ StreamStats stats_;
+ Linearizer linearizer_{kBuffer, &stats_};
};
} // namespace linearizer_fuzzer
diff --git a/src/connectivity/overnet/lib/datagram_stream/linearizer_test.cc b/src/connectivity/overnet/lib/datagram_stream/linearizer_test.cc
index a32ea0a..3cc7395 100644
--- a/src/connectivity/overnet/lib/datagram_stream/linearizer_test.cc
+++ b/src/connectivity/overnet/lib/datagram_stream/linearizer_test.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/datagram_stream/linearizer.h"
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/connectivity/overnet/lib/datagram_stream/linearizer_fuzzer.h"
@@ -20,6 +21,26 @@
template <class T>
void Ignore(T x) {}
+class StatsDumper final : public StatsVisitor {
+ public:
+ void Counter(const char* name, uint64_t value) override {
+ std::cout << name << " = " << value << "\n";
+
+ // Maintainer note:
+ // uncomment this line to get easily copy/pasted EXPECT macros to update the
+ // expectations in tests.
+
+ // std::cout << "EXPECT_EQ(stats." << name << ", uint64_t(" << value
+ // << "));\n";
+ }
+};
+
+template <class T>
+void DumpStats(const T* stats) {
+ StatsDumper dumper;
+ stats->Accept(&dumper);
+}
+
class MockCallbacks {
public:
MOCK_METHOD1(PullDone, void(const StatusOr<Optional<Slice>>&));
@@ -32,11 +53,15 @@
}
};
-TEST(Linearizer, NoOp) { Linearizer(1024); }
+TEST(Linearizer, NoOp) {
+ StreamStats stats;
+ Linearizer(1024, &stats);
+}
TEST(Linearizer, Push0_Pull0) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
// Push at offset 0 then a Pull should complete immediately
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
@@ -44,11 +69,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("a"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Pull0_Push0) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
// Push at offset 0 then a Pull should complete immediately
linearizer.Pull(cb.NewPull());
@@ -56,11 +105,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("a"))))));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push0_Push1_Pull0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
@@ -75,11 +148,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push1_Push0_Pull0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
@@ -94,11 +191,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push0_Pull0_Push1_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
@@ -113,11 +234,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(2));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push1_Pull0_Push0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
linearizer.Pull(cb.NewPull());
@@ -132,11 +277,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Pull0_Push1_Push0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
linearizer.Pull(cb.NewPull());
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
@@ -151,11 +320,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push0_Push0_Pull0) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
@@ -166,11 +359,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("a"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push0_PushBad0_Pull0) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("b")}));
@@ -178,11 +395,35 @@
EXPECT_CALL(cb, PullDone(Property(&StatusOr<Optional<Slice>>::is_ok, false)));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push1_Push01_Pull0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("ab")}));
@@ -197,11 +438,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("b"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(1));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push01_Push1_Pull0_Pull1) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("ab")}));
@@ -212,11 +477,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("ab"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push12_Push01_Pull0_Pull1_Pull2) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("bc")}));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("ab")}));
@@ -231,11 +520,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("bc"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(1));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push01_Push12_Pull0_Pull1_Pull2) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("ab")}));
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("bc")}));
@@ -250,21 +563,69 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("c"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push_Close) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("a")}));
// Final status will be an error because the message is incomplete.
EXPECT_TRUE(linearizer.Close(Status::Ok()).is_error());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push1_Push012_Pull0_Pull1_Pull2) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("abc")}));
@@ -285,11 +646,35 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("c"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(2));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
TEST(Linearizer, Push012_Push1_Pull0) {
StrictMock<MockCallbacks> cb;
- Linearizer linearizer(128);
+ StreamStats stats;
+ Linearizer linearizer(128, &stats);
Ignore(linearizer.Push(Chunk{0, false, Slice::FromStaticString("abc")}));
Ignore(linearizer.Push(Chunk{1, false, Slice::FromStaticString("b")}));
@@ -299,6 +684,29 @@
cb, PullDone(Property(&StatusOr<Optional<Slice>>::get,
Pointee(Pointee(Slice::FromStaticString("abc"))))));
linearizer.Pull(cb.NewPull());
+
+ DumpStats(&stats);
+
+ EXPECT_EQ(stats.linearizer_reject_past_end_of_buffering, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_empty_chunk, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_fast_path_taken, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_ignore_all_prior, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_partial_ignore_begin, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_new_pending_queue, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integrations, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_inserts, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_errors, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_shorter, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_coincident_longer, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_prior_longer, uint64_t(1));
+ EXPECT_EQ(stats.linearizer_integration_prior_partial, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_splits, uint64_t(0));
+ EXPECT_EQ(stats.linearizer_integration_subsequent_covers, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_cancel_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_split_packet_too_small, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_take_entire_chunk, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_nacked, uint64_t(0));
+ EXPECT_EQ(stats.send_chunk_push, uint64_t(0));
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/src/connectivity/overnet/lib/embedded/BUILD.gn b/src/connectivity/overnet/lib/embedded/BUILD.gn
index 5dff6aa..ac3e952 100644
--- a/src/connectivity/overnet/lib/embedded/BUILD.gn
+++ b/src/connectivity/overnet/lib/embedded/BUILD.gn
@@ -2,6 +2,25 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+###############################################################################
+# AGGREGATE LIBRARIES
+
+source_set("lib") {
+}
+
+source_set("tests") {
+ testonly = true
+ deps = []
+
+ if (!is_fuchsia) {
+ deps += [
+ ":stream_socket_link_test",
+ ]
+ }
+}
+
+###############################################################################
+
source_set("host_reactor") {
sources = [
"host_reactor.cc",
@@ -55,10 +74,10 @@
]
}
-source_set("embedded") {
+source_set("basic_embedded") {
sources = [
- "overnet_embedded.cc",
- "overnet_embedded.h",
+ "basic_overnet_embedded.cc",
+ "basic_overnet_embedded.h",
]
public_deps = [
":host_reactor",
@@ -68,13 +87,24 @@
]
}
+source_set("embedded") {
+ sources = [
+ "overnet_embedded.h",
+ ]
+ public_deps = [
+ ":basic_embedded",
+ ":stream_client",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ ]
+}
+
source_set("omdp_nub") {
sources = [
"omdp_nub.cc",
"omdp_nub.h",
]
public_deps = [
- ":embedded",
+ ":basic_embedded",
":udp_nub",
"//src/connectivity/overnet/lib/omdp",
"//src/connectivity/overnet/lib/vocabulary:ip_addr",
@@ -89,9 +119,71 @@
"udp_nub.h",
]
public_deps = [
- ":embedded",
+ ":basic_embedded",
"//src/connectivity/overnet/lib/links:packet_nub",
"//src/connectivity/overnet/lib/vocabulary:ip_addr",
"//src/connectivity/overnet/lib/vocabulary:socket",
]
}
+
+source_set("stream_socket_link") {
+ sources = [
+ "stream_socket_link.cc",
+ "stream_socket_link.h",
+ ]
+ deps = [
+ ":basic_embedded",
+ "//src/connectivity/overnet/lib/links:stream_link",
+ "//src/connectivity/overnet/lib/labels:node_id",
+ "//src/connectivity/overnet/lib/protocol:fidl",
+ "//src/connectivity/overnet/lib/protocol:stream_framer",
+ "//src/connectivity/overnet/lib/protocol:varint",
+ "//src/connectivity/overnet/lib/vocabulary:socket",
+ ]
+}
+
+source_set("stream_socket_link_test") {
+ testonly = true
+ sources = [
+ "stream_socket_link_test.cc",
+ ]
+ deps = [
+ "//third_party/googletest:gtest",
+ ":stream_socket_link",
+ "//src/connectivity/overnet/lib/environment:trace",
+ "//src/connectivity/overnet/lib/embedded:basic_embedded",
+ "//src/connectivity/overnet/lib/protocol:fidl",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
+ "//src/connectivity/overnet/lib/vocabulary:socket",
+ "//src/connectivity/overnet/lib/testing:flags",
+ ]
+}
+
+source_set("stream_server") {
+ sources = [
+ "stream_server.cc",
+ "stream_server.h",
+ ]
+ deps = [
+ ":basic_embedded",
+ ":stream_socket_link",
+ "//src/connectivity/overnet/lib/vocabulary:ip_addr",
+ "//src/connectivity/overnet/lib/vocabulary:socket",
+ "//src/connectivity/overnet/lib/protocol:stream_framer",
+ ]
+}
+
+source_set("stream_client") {
+ sources = [
+ "stream_client.cc",
+ "stream_client.h",
+ ]
+ deps = [
+ ":basic_embedded",
+ ":stream_socket_link",
+ "//src/connectivity/overnet/lib/vocabulary:ip_addr",
+ "//src/connectivity/overnet/lib/vocabulary:socket",
+ "//src/connectivity/overnet/lib/protocol:stream_framer",
+ ]
+}
diff --git a/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.cc b/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.cc
new file mode 100644
index 0000000..6b6a36c
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.cc
@@ -0,0 +1,124 @@
+// 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 "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+
+#include <random>
+
+namespace overnet {
+
+BasicOvernetEmbedded::BasicOvernetEmbedded(bool allow_non_determinism)
+ : endpoint_{&reactor_, GenerateNodeId(), allow_non_determinism},
+ shutdown_([this] {
+ bool done = false;
+ endpoint_.Close([&done] { done = true; });
+ auto last_print = timer()->Now();
+ while (!done) {
+ if (timer()->Now() - last_print > TimeDelta::FromSeconds(1)) {
+ OVERNET_TRACE(INFO) << "Waiting to exit";
+ last_print = timer()->Now();
+ }
+ reactor_.Step();
+ }
+ }) {}
+
+BasicOvernetEmbedded::~BasicOvernetEmbedded() = default;
+
+NodeId BasicOvernetEmbedded::GenerateNodeId() {
+ std::random_device rng_dev;
+ std::uniform_int_distribution<uint64_t> distrib;
+ return NodeId(distrib(rng_dev));
+}
+
+void BasicOvernetEmbedded::ListPeers(uint64_t last_seen_version,
+ ListPeersCallback callback) {
+ endpoint_.OnNodeDescriptionTableChange(
+ last_seen_version,
+ StatusCallback(ALLOCATED_CALLBACK, [this, callback = std::move(callback)](
+ const Status& status) {
+ if (status.is_error()) {
+ return;
+ }
+ std::vector<fuchsia::overnet::embedded::Peer> response;
+ auto new_version = endpoint_.ForEachNodeDescription(
+ [&response, self_node = endpoint_.node_id()](
+ overnet::NodeId id,
+ const fuchsia::overnet::protocol::PeerDescription& m) {
+ fuchsia::overnet::embedded::Peer peer;
+ peer.id = fidl::ToEmbedded(id.as_fidl());
+ peer.is_self = id == self_node;
+ peer.description = fidl::ToEmbedded(m);
+ response.emplace_back(std::move(peer));
+ });
+ callback(new_version, std::move(response));
+ }));
+}
+
+void BasicOvernetEmbedded::RegisterService(
+ std::string service_name,
+ std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>
+ service_provider) {
+ services_.emplace(std::move(service_name), std::move(service_provider));
+}
+
+void BasicOvernetEmbedded::ConnectToService(
+ fuchsia::overnet::protocol::embedded::NodeId node, std::string service_name,
+ ClosedPtr<ZxChannel> channel) {
+ if (node == fidl::ToEmbedded(endpoint_.node_id().as_fidl())) {
+ auto it = services_.find(service_name);
+ if (it != services_.end()) {
+ it->second->ConnectToService(std::move(channel));
+ } else {
+ OVERNET_TRACE(ERROR) << "Service not found: " << service_name;
+ }
+ return;
+ }
+ auto new_stream = endpoint_.InitiateStream(
+ NodeId(node.id),
+ fuchsia::overnet::protocol::ReliabilityAndOrdering::ReliableOrdered,
+ service_name);
+ if (new_stream.is_error()) {
+ OVERNET_TRACE(ERROR) << "Failed to create stream to initiate service: "
+ << service_name;
+ return;
+ }
+ channel->Bind(std::move(*new_stream));
+}
+
+BasicOvernetEmbedded::Actor::Actor(BasicOvernetEmbedded* root) : root_(root) {
+ assert(root->actors_);
+ root->actors_->push_back(this);
+}
+
+BasicOvernetEmbedded::Actor::~Actor() {
+ if (root_->actors_) {
+ root_->actors_->erase(
+ std::remove(root_->actors_->begin(), root_->actors_->end(), this));
+ }
+}
+
+int BasicOvernetEmbedded::Run() {
+ auto shutdown = std::move(shutdown_);
+ ZX_ASSERT(!shutdown.empty());
+
+ {
+ assert(actors_);
+ auto actors = std::move(actors_);
+ for (Actor* actor : *actors) {
+ if (auto status = actor->Start(); status.is_error()) {
+ OVERNET_TRACE(ERROR)
+ << "Failed to start actor '" << actor->Name() << "': " << status;
+ return 1;
+ }
+ }
+ }
+
+ if (auto status = reactor_.Run(); status.is_error()) {
+ OVERNET_TRACE(ERROR) << "Run loop failed: " << status;
+ return 1;
+ }
+ return 0;
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h b/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h
new file mode 100644
index 0000000..adf5bcf
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h
@@ -0,0 +1,81 @@
+// 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.
+
+#pragma once
+
+#include <fuchsia/overnet/cpp/overnet_embedded.h>
+
+#include "src/connectivity/overnet/lib/embedded/host_reactor.h"
+#include "src/connectivity/overnet/lib/endpoint/router_endpoint.h"
+#include "src/connectivity/overnet/lib/environment/trace_cout.h"
+
+namespace overnet {
+
+class BasicOvernetEmbedded : public fuchsia::overnet::embedded::Overnet {
+ public:
+ class Actor {
+ public:
+ Actor(BasicOvernetEmbedded* root);
+ virtual ~Actor();
+ virtual const char* Name() const = 0;
+ virtual Status Start() = 0;
+
+ protected:
+ BasicOvernetEmbedded* root() const { return root_; }
+
+ private:
+ BasicOvernetEmbedded* const root_;
+ };
+
+ BasicOvernetEmbedded(bool allow_non_determinism = true);
+ ~BasicOvernetEmbedded();
+
+ void ListPeers(uint64_t last_seen_version,
+ ListPeersCallback callback) override;
+ void RegisterService(
+ std::string service_name,
+ std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>
+ service_provider) override;
+ void ConnectToService(fuchsia::overnet::protocol::embedded::NodeId node,
+ std::string service_name,
+ ClosedPtr<ZxChannel> channel) override;
+
+ int Run();
+
+ void Exit(const Status& status) { reactor_.Exit(status); }
+
+ Timer* timer() { return &reactor_; }
+ NodeId node_id() { return endpoint_.node_id(); }
+ RouterEndpoint* endpoint() { return &endpoint_; }
+ HostReactor* reactor() { return &reactor_; }
+
+ private:
+ static NodeId GenerateNodeId();
+ void MaybeShutdown();
+
+ TraceCout initial_log_{nullptr};
+ ScopedRenderer initial_renderer_{&initial_log_};
+ HostReactor reactor_;
+ TraceCout running_log_{&reactor_};
+ ScopedRenderer running_renderer_{&running_log_};
+ RouterEndpoint endpoint_{&reactor_, GenerateNodeId(), true};
+ std::map<std::string,
+ std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>>
+ services_;
+ std::unique_ptr<std::vector<Actor*>> actors_ =
+ std::make_unique<std::vector<Actor*>>();
+ Callback<void> shutdown_;
+};
+
+template <class Service>
+std::unique_ptr<typename Service::Proxy_> ConnectToService(
+ fuchsia::overnet::embedded::Overnet* overnet,
+ const fuchsia::overnet::protocol::embedded::NodeId& peer) {
+ auto [client, server] = ZxChannel::MakePair();
+ overnet->ConnectToService(fidl::Clone(peer), Service::Name_,
+ std::move(server));
+ return std::make_unique<typename Service::Proxy_>(std::move(client));
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/host_reactor.cc b/src/connectivity/overnet/lib/embedded/host_reactor.cc
index d0aed7d..36a6c49 100644
--- a/src/connectivity/overnet/lib/embedded/host_reactor.cc
+++ b/src/connectivity/overnet/lib/embedded/host_reactor.cc
@@ -19,6 +19,15 @@
for (auto tmr : pending_timeouts_) {
FireTimeout(tmr.second, overnet::Status::Cancelled());
}
+ auto fds = std::move(fds_);
+ fds.clear();
+}
+
+void HostReactor::CancelIO(int fd) {
+ if (auto it = fds_.find(fd); it != fds_.end()) {
+ auto st = std::move(it->second);
+ fds_.erase(it);
+ }
}
void HostReactor::InitTimeout(overnet::Timeout* timeout,
@@ -124,11 +133,22 @@
next = &next_store;
next->tv_sec = dt.as_us() / 1000000;
next->tv_nsec = 1000 * (dt.as_us() % 1000000);
+ } else {
+ next = &next_store;
+ next->tv_sec = 0;
+ next->tv_nsec = 0;
}
}
- // Actually poll.
+// Actually poll.
+#if __linux__
+ // Use ppoll for higher timing resolution
int r = ppoll(pollfds.data(), pollfds.size(), next, nullptr);
+#else
+ // Use poll for compatibility
+ int r = poll(pollfds.data(), pollfds.size(),
+ next ? next->tv_sec * 1000 + next->tv_nsec / 1000000 : -1);
+#endif
if (r < 0) {
const int e = errno;
if (e == EINTR) {
diff --git a/src/connectivity/overnet/lib/embedded/host_reactor.h b/src/connectivity/overnet/lib/embedded/host_reactor.h
index 0de8d96..79c2faf 100644
--- a/src/connectivity/overnet/lib/embedded/host_reactor.h
+++ b/src/connectivity/overnet/lib/embedded/host_reactor.h
@@ -5,9 +5,11 @@
#pragma once
#include <time.h>
+
#include <map>
#include <mutex>
#include <unordered_map>
+
#include "src/connectivity/overnet/lib/environment/timer.h"
namespace overnet {
@@ -47,11 +49,40 @@
Status Run();
void Step();
- void OnRead(int fd, StatusCallback cb) { fds_[fd].on_read = std::move(cb); }
- void OnWrite(int fd, StatusCallback cb) { fds_[fd].on_read = std::move(cb); }
+ void OnRead(int fd, StatusCallback cb) {
+ if (!shutting_down_) {
+ fds_[fd].on_read = std::move(cb);
+ }
+ }
+ void OnWrite(int fd, StatusCallback cb) {
+ if (!shutting_down_) {
+ fds_[fd].on_write = std::move(cb);
+ }
+ }
+ void CancelIO(int fd);
void Exit(const Status& status) { exit_status_ = status; }
+ // Wait until either succeeds() returns true, or false if until is reached.
+ // Return true if succeeds(), otherwise false.
+ template <class F>
+ bool WaitUntil(F succeeds, TimeStamp until) {
+ if (succeeds()) {
+ return true;
+ }
+ bool timeout_hit = false;
+ Timeout timeout(this, until, [&timeout_hit](const Status& status) {
+ timeout_hit = true;
+ });
+ while (!timeout_hit) {
+ Step();
+ if (succeeds()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private:
void InitTimeout(Timeout* timeout, TimeStamp when) override;
void CancelTimeout(Timeout* timeout, Status status) override;
diff --git a/src/connectivity/overnet/lib/embedded/omdp_nub.cc b/src/connectivity/overnet/lib/embedded/omdp_nub.cc
index 6dbf59c..06d8984 100644
--- a/src/connectivity/overnet/lib/embedded/omdp_nub.cc
+++ b/src/connectivity/overnet/lib/embedded/omdp_nub.cc
@@ -16,18 +16,18 @@
std::random_device rng_dev;
static const size_t kMaximumPacketSize = 1400;
-OmdpNub::OmdpNub(OvernetEmbedded* root, UdpNub* udp_nub)
- : OvernetEmbedded::Actor(root),
+OmdpNub::OmdpNub(BasicOvernetEmbedded* root, UdpNub* udp_nub)
+ : BasicOvernetEmbedded::Actor(root),
Omdp(root->node_id().get(), root->timer(), []() { return rng_dev(); }),
reactor_(root->reactor()),
udp_nub_(udp_nub),
- timer_(root->timer()),
- incoming_(socket(AF_INET6, SOCK_DGRAM, 0)) {}
+ timer_(root->timer()) {}
OmdpNub::~OmdpNub() = default;
Status OmdpNub::Start() {
- return incoming_.SetOptReusePort(true)
+ return incoming_.Create(AF_INET6, SOCK_DGRAM, 0)
+ .Then([&] { return incoming_.SetOptReusePort(true); })
.Then([&] {
auto addr = *IpAddr::AnyIpv6().WithPort(kMulticastGroupAddr.port());
OVERNET_TRACE(DEBUG) << "Bind incoming OMDP socket to: " << addr;
@@ -53,6 +53,8 @@
return;
}
OVERNET_TRACE(DEBUG) << "BROADCAST " << data << " to " << kMulticastGroupAddr;
+ std::vector<Status> failures;
+ bool any_successes = false;
for (size_t i = 0;
interfaces[i].if_index != 0 || interfaces[i].if_name != nullptr; i++) {
if (auto status =
@@ -65,7 +67,14 @@
out << "On " << interfaces[i].if_name;
return out.str();
});
- status.is_error()) {
+ status.is_ok()) {
+ any_successes = true;
+ } else {
+ failures.emplace_back(std::move(status));
+ }
+ }
+ if (!any_successes) {
+ for (const auto& status : failures) {
OVERNET_TRACE(WARNING) << "Omdp broadcast failed: " << status;
}
}
@@ -73,7 +82,7 @@
}
void OmdpNub::OnNewNode(uint64_t node_id, IpAddr addr) {
- udp_nub_->Initiate(addr, NodeId(node_id));
+ udp_nub_->Initiate({addr}, NodeId(node_id));
}
void OmdpNub::WaitForInbound() {
diff --git a/src/connectivity/overnet/lib/embedded/omdp_nub.h b/src/connectivity/overnet/lib/embedded/omdp_nub.h
index 85299ac..a9ea5ff 100644
--- a/src/connectivity/overnet/lib/embedded/omdp_nub.h
+++ b/src/connectivity/overnet/lib/embedded/omdp_nub.h
@@ -5,16 +5,18 @@
#pragma once
#include <fbl/ref_ptr.h>
+
#include "src/connectivity/overnet/lib/embedded/udp_nub.h"
#include "src/connectivity/overnet/lib/omdp/omdp.h"
namespace overnet {
-class OmdpNub final : public OvernetEmbedded::Actor, private Omdp {
+class OmdpNub final : public BasicOvernetEmbedded::Actor, private Omdp {
public:
- OmdpNub(OvernetEmbedded* root, UdpNub* udp_nub);
+ OmdpNub(BasicOvernetEmbedded* root, UdpNub* udp_nub);
~OmdpNub();
Status Start() override;
+ const char* Name() const override { return "OmdpNub"; }
private:
void OnNewNode(uint64_t node_id, IpAddr addr) override;
diff --git a/src/connectivity/overnet/lib/embedded/overnet_embedded.cc b/src/connectivity/overnet/lib/embedded/overnet_embedded.cc
deleted file mode 100644
index 3ec6edf..0000000
--- a/src/connectivity/overnet/lib/embedded/overnet_embedded.cc
+++ /dev/null
@@ -1,114 +0,0 @@
-// 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 "src/connectivity/overnet/lib/embedded/overnet_embedded.h"
-#include <random>
-
-namespace overnet {
-
-OvernetEmbedded::OvernetEmbedded()
- : shutdown_([this] {
- bool done = false;
- endpoint_.Close([&done] { done = true; });
- while (!done) {
- OVERNET_TRACE(INFO) << "Waiting to exit";
- reactor_.Step();
- }
- }) {}
-
-OvernetEmbedded::~OvernetEmbedded() = default;
-
-NodeId OvernetEmbedded::GenerateNodeId() {
- std::random_device rng_dev;
- std::uniform_int_distribution<uint64_t> distrib;
- return NodeId(distrib(rng_dev));
-}
-
-void OvernetEmbedded::ListPeers(uint64_t last_seen_version,
- ListPeersCallback callback) {
- endpoint_.OnNodeDescriptionTableChange(
- last_seen_version,
- Callback<void>(
- ALLOCATED_CALLBACK, [this, callback = std::move(callback)] {
- std::vector<fuchsia::overnet::embedded::Peer> response;
- auto new_version = endpoint_.ForEachNodeDescription(
- [&response, self_node = endpoint_.node_id()](
- overnet::NodeId id,
- const fuchsia::overnet::protocol::PeerDescription& m) {
- fuchsia::overnet::embedded::Peer peer;
- peer.id = fidl::ToEmbedded(id.as_fidl());
- peer.is_self = id == self_node;
- peer.description = fidl::ToEmbedded(m);
- response.emplace_back(std::move(peer));
- });
- callback(new_version, std::move(response));
- }));
-}
-
-void OvernetEmbedded::RegisterService(
- std::string service_name,
- std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>
- service_provider) {
- services_.emplace(std::move(service_name), std::move(service_provider));
-}
-
-void OvernetEmbedded::ConnectToService(
- fuchsia::overnet::protocol::embedded::NodeId node, std::string service_name,
- ClosedPtr<ZxChannel> channel) {
- if (node == fidl::ToEmbedded(endpoint_.node_id().as_fidl())) {
- auto it = services_.find(service_name);
- if (it != services_.end()) {
- it->second->ConnectToService(std::move(channel));
- } else {
- OVERNET_TRACE(ERROR) << "Service not found: " << service_name;
- }
- return;
- }
- auto new_stream = endpoint_.InitiateStream(
- NodeId(node.id),
- fuchsia::overnet::protocol::ReliabilityAndOrdering::ReliableOrdered,
- service_name);
- if (new_stream.is_error()) {
- OVERNET_TRACE(ERROR) << "Failed to create stream to initiate service: "
- << service_name;
- return;
- }
- channel->Bind(std::move(*new_stream));
-}
-
-OvernetEmbedded::Actor::Actor(OvernetEmbedded* root) : root_(root) {
- assert(root->actors_);
- root->actors_->push_back(this);
-}
-
-OvernetEmbedded::Actor::~Actor() {
- if (root_->actors_) {
- root_->actors_->erase(
- std::remove(root_->actors_->begin(), root_->actors_->end(), this));
- }
-}
-
-int OvernetEmbedded::Run() {
- auto shutdown = std::move(shutdown_);
- ZX_ASSERT(!shutdown.empty());
-
- {
- assert(actors_);
- auto actors = std::move(actors_);
- for (Actor* actor : *actors) {
- if (auto status = actor->Start(); status.is_error()) {
- OVERNET_TRACE(ERROR) << "Failed to start actor: " << status;
- return 1;
- }
- }
- }
-
- if (auto status = reactor_.Run(); status.is_error()) {
- OVERNET_TRACE(ERROR) << "Run loop failed: " << status;
- return 1;
- }
- return 0;
-}
-
-} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/overnet_embedded.h b/src/connectivity/overnet/lib/embedded/overnet_embedded.h
index 3063405..a82e725 100644
--- a/src/connectivity/overnet/lib/embedded/overnet_embedded.h
+++ b/src/connectivity/overnet/lib/embedded/overnet_embedded.h
@@ -4,76 +4,20 @@
#pragma once
-#include <fuchsia/overnet/cpp/overnet_embedded.h>
-#include "src/connectivity/overnet/lib/embedded/host_reactor.h"
-#include "src/connectivity/overnet/lib/endpoint/router_endpoint.h"
-#include "src/connectivity/overnet/lib/environment/trace_cout.h"
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+#include "src/connectivity/overnet/lib/embedded/stream_client.h"
+#include "src/connectivity/overnet/lib/protocol/reliable_framer.h"
namespace overnet {
-class OvernetEmbedded final : public fuchsia::overnet::embedded::Overnet {
+class OvernetEmbedded : public BasicOvernetEmbedded {
public:
- class Actor {
- public:
- Actor(OvernetEmbedded* root);
- virtual ~Actor();
- virtual Status Start() = 0;
-
- protected:
- OvernetEmbedded* root() const { return root_; }
-
- private:
- OvernetEmbedded* const root_;
- };
-
- OvernetEmbedded();
- ~OvernetEmbedded();
-
- void ListPeers(uint64_t last_seen_version,
- ListPeersCallback callback) override;
- void RegisterService(
- std::string service_name,
- std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>
- service_provider) override;
- void ConnectToService(fuchsia::overnet::protocol::embedded::NodeId node,
- std::string service_name,
- ClosedPtr<ZxChannel> channel) override;
-
- int Run();
-
- void Exit(const Status& status) { reactor_.Exit(status); }
-
- Timer* timer() { return &reactor_; }
- NodeId node_id() { return endpoint_.node_id(); }
- RouterEndpoint* endpoint() { return &endpoint_; }
- HostReactor* reactor() { return &reactor_; }
+ OvernetEmbedded(
+ IpAddr ascendd_addr = *overnet::IpAddr::Unix("/tmp/ascendd.socket"))
+ : stream_client_{this, ascendd_addr} {}
private:
- static NodeId GenerateNodeId();
- void MaybeShutdown();
-
- TraceCout initial_log_{nullptr};
- ScopedRenderer initial_renderer_{&initial_log_};
- HostReactor reactor_;
- TraceCout running_log_{&reactor_};
- ScopedRenderer running_renderer_{&running_log_};
- RouterEndpoint endpoint_{&reactor_, GenerateNodeId(), true};
- std::map<std::string,
- std::unique_ptr<fuchsia::overnet::embedded::ServiceProvider_Proxy>>
- services_;
- std::unique_ptr<std::vector<Actor*>> actors_ =
- std::make_unique<std::vector<Actor*>>();
- Callback<void> shutdown_;
+ overnet::StreamClient<overnet::ReliableFramer> stream_client_;
};
-template <class Service>
-std::unique_ptr<typename Service::Proxy_> ConnectToService(
- fuchsia::overnet::embedded::Overnet* overnet,
- const fuchsia::overnet::protocol::embedded::NodeId& peer) {
- auto [client, server] = ZxChannel::MakePair();
- overnet->ConnectToService(fidl::Clone(peer), Service::Name_,
- std::move(server));
- return std::make_unique<typename Service::Proxy_>(std::move(client));
-}
-
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_client.cc b/src/connectivity/overnet/lib/embedded/stream_client.cc
new file mode 100644
index 0000000..3640a378
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_client.cc
@@ -0,0 +1,30 @@
+// 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 "src/connectivity/overnet/lib/embedded/stream_client.h"
+
+#include "src/connectivity/overnet/lib/embedded/stream_socket_link.h"
+#include "src/connectivity/overnet/lib/vocabulary/socket.h"
+
+namespace overnet {
+
+StreamClientBase::StreamClientBase(BasicOvernetEmbedded* app, IpAddr target)
+ : BasicOvernetEmbedded::Actor(app), target_(target) {}
+
+Status StreamClientBase::Start() {
+ Socket socket;
+ return socket.Create(target_.addr.sa_family, SOCK_STREAM, 0)
+ .Then([&] { return socket.Connect(target_); })
+ .Then([&] {
+ RegisterStreamSocketLink(
+ root(), std::move(socket), CreateFramer(), true,
+ TimeDelta::PositiveInf(), [app = root()] {
+ app->Exit(Status(StatusCode::UNAVAILABLE,
+ "Stream server disconnected"));
+ });
+ return Status::Ok();
+ });
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_client.h b/src/connectivity/overnet/lib/embedded/stream_client.h
new file mode 100644
index 0000000..6fb2e03
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_client.h
@@ -0,0 +1,38 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
+#include "src/connectivity/overnet/lib/vocabulary/ip_addr.h"
+
+namespace overnet {
+
+class StreamClientBase : public BasicOvernetEmbedded::Actor {
+ public:
+ StreamClientBase(BasicOvernetEmbedded* app, IpAddr target);
+
+ const char* Name() const override final { return "StreamClient"; }
+ Status Start() override final;
+
+ private:
+ const IpAddr target_;
+
+ virtual std::unique_ptr<StreamFramer> CreateFramer() const = 0;
+};
+
+template <class T>
+class StreamClient final : public StreamClientBase {
+ public:
+ StreamClient(BasicOvernetEmbedded* app, IpAddr target)
+ : StreamClientBase(app, target) {}
+
+ private:
+ std::unique_ptr<StreamFramer> CreateFramer() const override {
+ return std::make_unique<T>();
+ }
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_server.cc b/src/connectivity/overnet/lib/embedded/stream_server.cc
new file mode 100644
index 0000000..83b849a
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_server.cc
@@ -0,0 +1,46 @@
+// 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 "src/connectivity/overnet/lib/embedded/stream_server.h"
+
+#include "src/connectivity/overnet/lib/embedded/stream_socket_link.h"
+
+namespace overnet {
+
+StreamServerBase::StreamServerBase(BasicOvernetEmbedded* app, IpAddr bind)
+ : Actor(app), app_(app), bind_(bind) {}
+
+Status StreamServerBase::Start() {
+ return listener_.Create(bind_.addr.sa_family, SOCK_STREAM, 0)
+ .Then([&] { return listener_.Bind(bind_); })
+ .Then([&] { return listener_.Listen(); })
+ .Then([&] {
+ AwaitRead();
+ return Status::Ok();
+ })
+ .WithLazyContext([this] {
+ std::ostringstream out;
+ out << "Building stream server " << bind_;
+ return out.str();
+ });
+}
+
+void StreamServerBase::AwaitRead() {
+ app_->reactor()->OnRead(listener_.get(), [&](const Status& status) {
+ if (status.is_error()) {
+ return;
+ }
+ auto fd = listener_.Accept();
+ if (fd.is_error()) {
+ OVERNET_TRACE(ERROR) << "Failed to accept socket: " << fd.AsStatus();
+ return;
+ }
+ RegisterStreamSocketLink(app_, std::move(*fd), CreateFramer(), true,
+ TimeDelta::PositiveInf(),
+ Callback<void>::Ignored());
+ AwaitRead();
+ });
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_server.h b/src/connectivity/overnet/lib/embedded/stream_server.h
new file mode 100644
index 0000000..f383906
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_server.h
@@ -0,0 +1,42 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
+#include "src/connectivity/overnet/lib/vocabulary/ip_addr.h"
+#include "src/connectivity/overnet/lib/vocabulary/socket.h"
+
+namespace overnet {
+
+class StreamServerBase : public BasicOvernetEmbedded::Actor {
+ public:
+ StreamServerBase(BasicOvernetEmbedded* app, IpAddr bind);
+
+ const char* Name() const override { return "StreamServer"; }
+ Status Start() override;
+
+ private:
+ void AwaitRead();
+ virtual std::unique_ptr<StreamFramer> CreateFramer() = 0;
+
+ BasicOvernetEmbedded* const app_;
+ const IpAddr bind_;
+ Socket listener_;
+};
+
+template <class T>
+class StreamServer final : public StreamServerBase {
+ public:
+ StreamServer(BasicOvernetEmbedded* app, IpAddr bind)
+ : StreamServerBase(app, bind) {}
+
+ private:
+ virtual std::unique_ptr<StreamFramer> CreateFramer() {
+ return std::make_unique<T>();
+ }
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_socket_link.cc b/src/connectivity/overnet/lib/embedded/stream_socket_link.cc
new file mode 100644
index 0000000..91992a6
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_socket_link.cc
@@ -0,0 +1,283 @@
+// 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 "src/connectivity/overnet/lib/embedded/stream_socket_link.h"
+
+#include <fuchsia/overnet/protocol/cpp/fidl.h>
+
+#include "src/connectivity/overnet/lib/labels/node_id.h"
+#include "src/connectivity/overnet/lib/links/stream_link.h"
+#include "src/connectivity/overnet/lib/protocol/fidl.h"
+#include "src/connectivity/overnet/lib/vocabulary/optional.h"
+
+namespace overnet {
+
+namespace {
+struct ValidatedArgs {
+ NodeId peer;
+ uint64_t remote_link_label;
+};
+
+class Link final : public StreamLink {
+ public:
+ Link(BasicOvernetEmbedded* app, const ValidatedArgs& args,
+ uint64_t local_label, std::unique_ptr<StreamFramer> framer,
+ Socket socket, Callback<void> destroyed)
+ : StreamLink(app->endpoint(), args.peer, std::move(framer), local_label),
+ reactor_(app->reactor()),
+ socket_(std::move(socket)),
+ destroyed_(std::move(destroyed)) {
+ BeginRead();
+ }
+
+ ~Link() {
+ if (socket_.IsValid()) {
+ auto socket = std::move(socket_);
+ reactor_->CancelIO(socket.get());
+ }
+ }
+
+ void Emit(Slice slice, Callback<Status> done) override {
+ auto status = socket_.Write(slice);
+ if (status.is_error()) {
+ done(status.AsStatus());
+ return;
+ }
+ if (status->length() > 0) {
+ reactor_->OnWrite(
+ socket_.get(),
+ StatusCallback(ALLOCATED_CALLBACK,
+ [this, slice = std::move(slice),
+ done = std::move(done)](Status status) mutable {
+ if (status.is_error()) {
+ done(std::move(status));
+ return;
+ }
+ Emit(std::move(slice), std::move(done));
+ }));
+ return;
+ }
+ done(Status::Ok());
+ }
+
+ private:
+ void BeginRead() {
+ reactor_->OnRead(socket_.get(), [this](const Status& status) {
+ if (status.is_error()) {
+ return;
+ }
+ TimeStamp now = reactor_->Now();
+ // Read some data. Choose a read size of maximum_segment_size + epsilon to
+ // try and pull in full segments at a time.
+ auto read = socket_.Read(maximum_segment_size() + 64);
+ if (read.is_ok() && read->has_value()) {
+ Process(now, std::move(**read));
+ BeginRead();
+ } else {
+ OVERNET_TRACE(ERROR) << read;
+ }
+ });
+ }
+
+ HostReactor* const reactor_;
+ Socket socket_;
+ Callback<void> destroyed_;
+};
+
+class Handshake {
+ public:
+ static inline const std::string kGreetingString = "Fuchsia Socket Stream";
+
+ Handshake(BasicOvernetEmbedded* app, Socket socket,
+ std::unique_ptr<StreamFramer> framer, bool eager_announce,
+ TimeDelta read_timeout, Callback<void> destroyed)
+ : app_(app),
+ socket_(std::move(socket)),
+ framer_(std::move(framer)),
+ link_label_(app->endpoint()->GenerateLinkLabel()),
+ destroyed_(std::move(destroyed)),
+ eager_announce_(eager_announce),
+ read_timeout_(read_timeout) {
+ if (eager_announce) {
+ SendGreeting();
+ }
+ AwaitGreeting();
+ }
+
+ private:
+ void DoneWriting(Status status) {
+ OVERNET_TRACE(DEBUG) << "Finished writing stream handshake: " << status;
+ if (status.is_error() && socket_.IsValid()) {
+ app_->reactor()->CancelIO(socket_.get());
+ socket_.Close();
+ }
+ Done();
+ }
+
+ void DoneReading(Status status) {
+ OVERNET_TRACE(DEBUG) << "Finished reading stream handshake: " << status;
+ if (status.is_error() && socket_.IsValid()) {
+ app_->reactor()->CancelIO(socket_.get());
+ socket_.Close();
+ } else if (!eager_announce_) {
+ SendGreeting();
+ }
+ Done();
+ }
+
+ void Done() {
+ if (--dones_pending_ == 0) {
+ if (socket_.IsValid()) {
+ app_->reactor()->CancelIO(socket_.get());
+ app_->endpoint()->RegisterPeer(validated_args_->peer);
+ app_->endpoint()->RegisterLink(MakeLink<Link>(
+ app_, *validated_args_, link_label_, std::move(framer_),
+ std::move(socket_), std::move(destroyed_)));
+ }
+
+ delete this;
+ }
+ }
+
+ void SendGreeting() {
+ fuchsia::overnet::protocol::StreamSocketGreeting send_greeting;
+ send_greeting.set_magic_string(kGreetingString);
+ send_greeting.set_node_id(app_->node_id().as_fidl());
+ send_greeting.set_local_link_id(link_label_);
+ auto bytes = Encode(&send_greeting);
+ if (bytes.is_error()) {
+ DoneWriting(bytes.AsStatus());
+ return;
+ }
+
+ SendBytes(framer_->Frame(std::move(*bytes)));
+ }
+
+ void SendBytes(Slice bytes) {
+ app_->reactor()->OnWrite(
+ socket_.get(),
+ StatusCallback(ALLOCATED_CALLBACK, [this, bytes = std::move(bytes)](
+ const Status& status) mutable {
+ if (status.is_error()) {
+ DoneWriting(status);
+ return;
+ }
+
+ auto write_status = socket_.Write(bytes);
+ if (write_status.is_error()) {
+ DoneWriting(write_status.AsStatus());
+ return;
+ }
+ if (write_status->length() > 0) {
+ SendBytes(std::move(*write_status));
+ return;
+ }
+ DoneWriting(Status::Ok());
+ }));
+ }
+
+ void AwaitGreeting() {
+ app_->reactor()->OnRead(socket_.get(), [this](const Status& status) {
+ if (status.is_error()) {
+ DoneReading(status);
+ return;
+ }
+
+ auto read = socket_.Read(2 * framer_->maximum_segment_size);
+ if (read.is_error()) {
+ DoneReading(read.AsStatus());
+ return;
+ }
+ if (!read->has_value()) {
+ DoneReading(Status(StatusCode::UNKNOWN, "End of file handshaking"));
+ return;
+ }
+
+ framer_->Push(std::move(**read));
+
+ if (!ContinueReading()) {
+ AwaitGreeting();
+ }
+ });
+ }
+
+ // Returns true if reading is done.
+ bool ContinueReading() {
+ auto frame = framer_->Pop();
+ if (frame.is_error()) {
+ DoneReading(frame.AsStatus().WithContext("Handshaking stream link"));
+ return true;
+ }
+ if (!frame->has_value()) {
+ if (read_timeout_ != TimeDelta::PositiveInf()) {
+ skip_timeout_.Reset(app_->timer(), app_->timer()->Now() + read_timeout_,
+ [this](const Status& status) {
+ if (status.is_error()) {
+ return;
+ }
+ auto noise = framer_->SkipNoise();
+ OVERNET_TRACE(DEBUG)
+ << "Skip input noise: " << noise;
+ ContinueReading();
+ });
+ }
+ return false;
+ }
+
+ auto decoded = Decode<fuchsia::overnet::protocol::StreamSocketGreeting>(
+ std::move(**frame));
+ if (decoded.is_error()) {
+ DoneReading(decoded.AsStatus());
+ return true;
+ }
+
+ if (!decoded->has_magic_string()) {
+ DoneReading(Status(StatusCode::INVALID_ARGUMENT, "No magic string"));
+ return true;
+ }
+ if (decoded->magic_string() != kGreetingString) {
+ DoneReading(Status(StatusCode::INVALID_ARGUMENT, "Bad magic string"));
+ return true;
+ }
+ if (!decoded->has_node_id()) {
+ DoneReading(Status(StatusCode::INVALID_ARGUMENT, "No node id"));
+ return true;
+ }
+ if (!decoded->has_local_link_id()) {
+ DoneReading(Status(StatusCode::INVALID_ARGUMENT, "No local link id"));
+ return true;
+ }
+ validated_args_.Reset(
+ ValidatedArgs{decoded->node_id(), decoded->local_link_id()});
+ DoneReading(Status::Ok());
+ return true;
+ }
+
+ BasicOvernetEmbedded* const app_;
+ Socket socket_;
+ std::unique_ptr<StreamFramer> framer_;
+ const uint64_t link_label_;
+ int dones_pending_ = 2;
+ Optional<ValidatedArgs> validated_args_;
+ Callback<void> destroyed_;
+ const bool eager_announce_;
+ const TimeDelta read_timeout_;
+ Optional<Timeout> skip_timeout_;
+};
+} // namespace
+
+void RegisterStreamSocketLink(BasicOvernetEmbedded* app, Socket socket,
+ std::unique_ptr<StreamFramer> framer,
+ bool eager_announce, TimeDelta read_timeout,
+ Callback<void> destroyed) {
+ if (auto status = socket.SetNonBlocking(true); status.is_error()) {
+ OVERNET_TRACE(WARNING)
+ << "Failed to set non-blocking for stream link socket: " << status;
+ return;
+ }
+ new Handshake(app, std::move(socket), std::move(framer), eager_announce,
+ read_timeout, std::move(destroyed));
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_socket_link.h b/src/connectivity/overnet/lib/embedded/stream_socket_link.h
new file mode 100644
index 0000000..5797909
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_socket_link.h
@@ -0,0 +1,19 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+#include "src/connectivity/overnet/lib/links/stream_link.h"
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
+#include "src/connectivity/overnet/lib/vocabulary/socket.h"
+
+namespace overnet {
+
+void RegisterStreamSocketLink(BasicOvernetEmbedded* app, Socket socket,
+ std::unique_ptr<StreamFramer> framer,
+ bool eager_announce, TimeDelta read_timeout,
+ Callback<void> destroyed);
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/stream_socket_link_test.cc b/src/connectivity/overnet/lib/embedded/stream_socket_link_test.cc
new file mode 100644
index 0000000..43d0e0c
--- /dev/null
+++ b/src/connectivity/overnet/lib/embedded/stream_socket_link_test.cc
@@ -0,0 +1,207 @@
+// 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 "src/connectivity/overnet/lib/embedded/stream_socket_link.h"
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.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/testing/flags.h"
+#include "src/connectivity/overnet/lib/vocabulary/socket.h"
+
+namespace overnet {
+namespace stream_socket_link_test {
+
+template <class F, bool E, bool RT>
+struct TestTraits {
+ using Framer = F;
+ static constexpr bool kEagerAnnounce = E;
+ static constexpr TimeDelta kReadTimeout =
+ RT ? TimeDelta::FromMilliseconds(100) : TimeDelta::PositiveInf();
+};
+
+using ReliableEager = TestTraits<ReliableFramer, true, false>;
+using ReliableUneager = TestTraits<ReliableFramer, false, false>;
+using UnreliableEager = TestTraits<UnreliableFramer, true, true>;
+using UnreliableUneager = TestTraits<UnreliableFramer, false, true>;
+
+template <class Traits>
+class TypedTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ int sv[2];
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sv));
+ mocked_socket_ = Socket(sv[1]);
+ ASSERT_TRUE(mocked_socket_.SetNonBlocking(true).is_ok());
+ RegisterStreamSocketLink(&app_, Socket(sv[0]),
+ std::make_unique<typename Traits::Framer>(),
+ Traits::kEagerAnnounce, Traits::kReadTimeout,
+ [this] { destroyed_ = true; });
+ }
+
+ void TearDown() override {
+ app_.Exit(Status::Ok());
+ app_.Run();
+ }
+
+ void Write(Slice stuff) {
+ auto write_result = mocked_socket_.Write(std::move(stuff));
+ ASSERT_TRUE(write_result.is_ok()) << write_result;
+ if (write_result->length()) {
+ bool done = false;
+ app_.reactor()->OnWrite(
+ mocked_socket_.get(),
+ StatusCallback(ALLOCATED_CALLBACK,
+ [this, &done, remaining = std::move(*write_result)](
+ const Status& status) mutable {
+ ASSERT_TRUE(status.is_ok()) << status;
+ done = true;
+ Write(std::move(remaining));
+ }));
+ StepUntil(&done);
+ }
+ }
+
+ StatusOr<Optional<Slice>> PollFrame() { return mocked_side_framer_.Pop(); }
+
+ StatusOr<Slice> ReadFrame() {
+ auto poll_result = PollFrame();
+ if (poll_result.is_error()) {
+ return poll_result.AsStatus();
+ }
+ if (poll_result->has_value()) {
+ return std::move(**poll_result);
+ }
+ bool done = false;
+ app_.reactor()->OnRead(
+ mocked_socket_.get(),
+ StatusCallback(ALLOCATED_CALLBACK, [this, &done](const Status& status) {
+ ASSERT_TRUE(status.is_ok()) << status;
+ done = true;
+ auto input = mocked_socket_.Read(4096);
+ ASSERT_TRUE(input.is_ok()) << input;
+ if (input->has_value()) {
+ mocked_side_framer_.Push(std::move(**input));
+ }
+ }));
+ StepUntil(&done);
+ return ReadFrame();
+ }
+
+ template <class F>
+ StatusOr<F> ReadAndDecode() {
+ return ReadFrame().Then(
+ [](Slice slice) { return Decode<F>(std::move(slice)); });
+ }
+
+ Slice Frame(Slice stuff) { return mocked_side_framer_.Frame(stuff); }
+
+ bool RouterHasRouteTo(NodeId node_id) {
+ return app_.endpoint()->HasRouteTo(node_id);
+ }
+
+ template <class F>
+ bool WaitFor(F succeeds, TimeDelta timeout) {
+ return app_.reactor()->WaitUntil(std::move(succeeds),
+ app_.timer()->Now() + timeout);
+ }
+
+ void StepUntil(bool* done) {
+ Timeout timeout(app_.timer(),
+ app_.timer()->Now() + TimeDelta::FromSeconds(5),
+ [](const Status& status) {
+ if (status.is_ok()) {
+ abort();
+ }
+ });
+ while (!*done) {
+ app_.reactor()->Step();
+ }
+ }
+
+ private:
+ BasicOvernetEmbedded app_{false};
+ ScopedSeverity scoped_severity{FLAGS_verbose ? Severity::DEBUG
+ : Severity::INFO};
+ typename Traits::Framer mocked_side_framer_;
+ Socket mocked_socket_;
+ bool destroyed_ = false;
+};
+
+TYPED_TEST_SUITE_P(TypedTest);
+
+static Slice EncodedGreeting() {
+ fuchsia::overnet::protocol::StreamSocketGreeting greeting;
+ greeting.set_magic_string("Fuchsia Socket Stream");
+ greeting.set_node_id(NodeId(0x1111'2222'3333'4444).as_fidl());
+ greeting.set_local_link_id(1);
+ return *Encode(&greeting);
+}
+
+TYPED_TEST_P(TypedTest, Connects) {
+ this->Write(this->Frame(EncodedGreeting()));
+
+ auto got_greeting = this->template ReadAndDecode<
+ fuchsia::overnet::protocol::StreamSocketGreeting>();
+ ASSERT_TRUE(got_greeting.is_ok()) << got_greeting;
+ ASSERT_TRUE(got_greeting->has_magic_string());
+ EXPECT_EQ(got_greeting->magic_string(), "Fuchsia Socket Stream");
+ EXPECT_TRUE(got_greeting->has_node_id());
+ EXPECT_TRUE(got_greeting->has_local_link_id());
+
+ EXPECT_TRUE(this->WaitFor(
+ [this] { return this->RouterHasRouteTo(NodeId(0x1111'2222'3333'4444)); },
+ TimeDelta::FromMilliseconds(200)));
+}
+
+REGISTER_TYPED_TEST_SUITE_P(TypedTest, Connects);
+using FramerTypes = ::testing::Types<ReliableEager, ReliableUneager,
+ UnreliableEager, UnreliableUneager>;
+INSTANTIATE_TYPED_TEST_SUITE_P(StreamSocketLinkSuite, TypedTest, FramerTypes);
+
+struct UnreliableUneagerTest : public TypedTest<UnreliableUneager>,
+ public ::testing::WithParamInterface<Slice> {};
+
+TEST_P(UnreliableUneagerTest, Connects) {
+ OVERNET_TRACE(INFO) << GetParam();
+ this->Write(GetParam());
+
+ auto got_greeting = this->template ReadAndDecode<
+ fuchsia::overnet::protocol::StreamSocketGreeting>();
+ ASSERT_TRUE(got_greeting.is_ok()) << got_greeting;
+ ASSERT_TRUE(got_greeting->has_magic_string());
+ EXPECT_EQ(got_greeting->magic_string(), "Fuchsia Socket Stream");
+ EXPECT_TRUE(got_greeting->has_node_id());
+ EXPECT_TRUE(got_greeting->has_local_link_id());
+
+ EXPECT_TRUE(this->WaitFor(
+ [this] { return this->RouterHasRouteTo(NodeId(0x1111'2222'3333'4444)); },
+ TimeDelta::FromMilliseconds(200)));
+}
+
+const Slice kUnreliableStreamGreetingFrame =
+ UnreliableFramer().Frame(EncodedGreeting());
+
+INSTANTIATE_TEST_SUITE_P(
+ UnreliableUneagerSuite, UnreliableUneagerTest,
+ ::testing::Values(
+ kUnreliableStreamGreetingFrame,
+ Slice::Join(
+ {kUnreliableStreamGreetingFrame,
+ Slice::FromStaticString("\nsome random trailing bytes\n")}),
+ Slice::Join(
+ {kUnreliableStreamGreetingFrame,
+ Slice::FromStaticString(
+ "some random trailing bytes without the extra newlines")}),
+ Slice::Join({Slice::FromStaticString("not a frame"),
+ kUnreliableStreamGreetingFrame}),
+ Slice::Join({Slice::FromStaticString("\n\xff"),
+ kUnreliableStreamGreetingFrame})));
+
+} // namespace stream_socket_link_test
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/embedded/udp_nub.cc b/src/connectivity/overnet/lib/embedded/udp_nub.cc
index 19f32e0..b26b464 100644
--- a/src/connectivity/overnet/lib/embedded/udp_nub.cc
+++ b/src/connectivity/overnet/lib/embedded/udp_nub.cc
@@ -3,14 +3,15 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/embedded/udp_nub.h"
+
#include <sys/socket.h>
#include <unistd.h>
namespace overnet {
-UdpNub::UdpNub(OvernetEmbedded* root)
- : OvernetEmbedded::Actor(root),
- UdpNubBase(root->timer(), root->node_id()),
+UdpNub::UdpNub(BasicOvernetEmbedded* root)
+ : BasicOvernetEmbedded::Actor(root),
+ UdpNubBase(root->endpoint()),
endpoint_(root->endpoint()),
timer_(root->timer()),
reactor_(root->reactor()) {}
@@ -67,13 +68,7 @@
WaitForInbound();
}
-Status UdpNub::CreateFD() {
- socket_ = Socket(::socket(AF_INET6, SOCK_DGRAM, 0));
- if (!socket_.IsValid()) {
- return StatusFromErrno("Failed to create socket");
- }
- return Status::Ok();
-}
+Status UdpNub::CreateFD() { return socket_.Create(AF_INET6, SOCK_DGRAM, 0); }
Status UdpNub::SetOptionSharePort() { return socket_.SetOptReusePort(true); }
diff --git a/src/connectivity/overnet/lib/embedded/udp_nub.h b/src/connectivity/overnet/lib/embedded/udp_nub.h
index e128c28..ca3f3f8 100644
--- a/src/connectivity/overnet/lib/embedded/udp_nub.h
+++ b/src/connectivity/overnet/lib/embedded/udp_nub.h
@@ -4,7 +4,7 @@
#pragma once
-#include "src/connectivity/overnet/lib/embedded/overnet_embedded.h"
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
#include "src/connectivity/overnet/lib/links/packet_nub.h"
#include "src/connectivity/overnet/lib/vocabulary/ip_addr.h"
#include "src/connectivity/overnet/lib/vocabulary/socket.h"
@@ -13,18 +13,17 @@
using UdpNubBase = PacketNub<IpAddr, 1500, HashIpAddr, EqIpAddr>;
-class UdpNub final : public OvernetEmbedded::Actor, public UdpNubBase {
+class UdpNub final : public BasicOvernetEmbedded::Actor, public UdpNubBase {
public:
- explicit UdpNub(OvernetEmbedded* master);
+ explicit UdpNub(BasicOvernetEmbedded* app);
~UdpNub();
+ const char* Name() const override { return "UdpNub"; }
Status Start() override;
uint16_t port() { return port_; }
NodeId node_id() { return endpoint_->node_id(); }
void SendTo(IpAddr addr, Slice slice) override;
- Router* GetRouter() override { return endpoint_; }
-
void Publish(overnet::LinkPtr<> link) override {
NodeId node = NodeId(link->GetLinkStatus().to);
OVERNET_TRACE(DEBUG) << "NewLink: " << node << "\n";
diff --git a/src/connectivity/overnet/lib/endpoint/BUILD.gn b/src/connectivity/overnet/lib/endpoint/BUILD.gn
index 1c6e692..d269217 100644
--- a/src/connectivity/overnet/lib/endpoint/BUILD.gn
+++ b/src/connectivity/overnet/lib/endpoint/BUILD.gn
@@ -50,7 +50,10 @@
":router_endpoint",
"//src/connectivity/overnet/lib/environment:trace_cout",
"//src/connectivity/overnet/lib/links:packet_link",
+ "//src/connectivity/overnet/lib/links:stream_link",
"//src/connectivity/overnet/lib/protocol:fidl",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
"//src/connectivity/overnet/lib/testing:flags",
"//src/connectivity/overnet/lib/testing:test_timer",
"//third_party/googletest:gtest",
diff --git a/src/connectivity/overnet/lib/endpoint/router_endpoint.cc b/src/connectivity/overnet/lib/endpoint/router_endpoint.cc
index 0748f52..f39271b 100644
--- a/src/connectivity/overnet/lib/endpoint/router_endpoint.cc
+++ b/src/connectivity/overnet/lib/endpoint/router_endpoint.cc
@@ -3,8 +3,10 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/endpoint/router_endpoint.h"
+
#include <iostream>
#include <memory>
+
#include "garnet/public/lib/fostr/fidl/fuchsia/overnet/protocol/formatting.h"
#include "src/connectivity/overnet/lib/protocol/coding.h"
#include "src/connectivity/overnet/lib/protocol/fidl.h"
@@ -43,15 +45,19 @@
}
void RouterEndpoint::SendGossipTo(NodeId target) {
- OVERNET_TRACE(DEBUG) << node_id() << " send gossip to " << target;
// Are we still gossiping?
if (!gossip_timer_.get()) {
+ OVERNET_TRACE(DEBUG) << node_id() << " send gossip to " << target
+ << " [skipped: STOPPED GOSSIPING]";
return;
}
auto con = connection_streams_.find(target);
if (con == connection_streams_.end()) {
+ OVERNET_TRACE(DEBUG) << node_id() << " send gossip to " << target
+ << " [skipped: NO CONNECTION]";
return;
}
+ OVERNET_TRACE(DEBUG) << node_id() << " send gossip to " << target;
SendGossipUpdate(con->second.proxy(), target);
}
@@ -406,7 +412,7 @@
}
void RouterEndpoint::OnNodeDescriptionTableChange(uint64_t last_seen_version,
- Callback<void> on_change) {
+ StatusCallback on_change) {
if (last_seen_version == node_description_table_version_) {
on_node_description_table_change_.emplace_back(std::move(on_change));
}
@@ -416,7 +422,9 @@
void RouterEndpoint::NewNodeDescriptionTableVersion() {
++node_description_table_version_;
- std::vector<Callback<void>>().swap(on_node_description_table_change_);
+ for (auto& cb : std::move(on_node_description_table_change_)) {
+ cb(Status::Ok());
+ }
}
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/endpoint/router_endpoint.h b/src/connectivity/overnet/lib/endpoint/router_endpoint.h
index 33ac895..e8a0535 100644
--- a/src/connectivity/overnet/lib/endpoint/router_endpoint.h
+++ b/src/connectivity/overnet/lib/endpoint/router_endpoint.h
@@ -5,7 +5,9 @@
#pragma once
#include <fuchsia/overnet/protocol/cpp/fidl.h>
+
#include <queue>
+
#include "garnet/public/lib/fostr/fidl/fuchsia/overnet/protocol/formatting.h"
#include "src/connectivity/overnet/lib/datagram_stream/datagram_stream.h"
#include "src/connectivity/overnet/lib/routing/router.h"
@@ -142,7 +144,7 @@
}
void OnNodeDescriptionTableChange(uint64_t last_seen_version,
- Callback<void> on_change);
+ StatusCallback on_change);
template <class F>
uint64_t ForEachNodeDescription(F f) {
@@ -254,7 +256,7 @@
std::unordered_map<NodeId, ConnectionStream> connection_streams_;
uint64_t node_description_table_version_ = 1;
- std::vector<Callback<void>> on_node_description_table_change_;
+ std::vector<StatusCallback> on_node_description_table_change_;
Optional<Timeout> gossip_timer_;
Optional<Timeout> description_timer_;
TimeDelta gossip_interval_ = InitialGossipInterval();
diff --git a/src/connectivity/overnet/lib/endpoint/router_endpoint_integration_test.cc b/src/connectivity/overnet/lib/endpoint/router_endpoint_integration_test.cc
index 076bc2d..463a817 100644
--- a/src/connectivity/overnet/lib/endpoint/router_endpoint_integration_test.cc
+++ b/src/connectivity/overnet/lib/endpoint/router_endpoint_integration_test.cc
@@ -4,11 +4,15 @@
#include <array>
#include <tuple>
+
#include "gtest/gtest.h"
#include "src/connectivity/overnet/lib/endpoint/router_endpoint.h"
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
#include "src/connectivity/overnet/lib/links/packet_link.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/testing/flags.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
@@ -23,40 +27,60 @@
int outstanding_packets;
};
-class DeliverySimulator {
+class Simulator {
public:
- virtual ~DeliverySimulator() {}
+ virtual ~Simulator() = default;
+ virtual void MakeLinks(RouterEndpoint* a, RouterEndpoint* b, uint64_t id1,
+ uint64_t id2) const = 0;
+};
+
+struct NamedSimulator {
+ std::string name;
+ std::unique_ptr<Simulator> simulator;
+};
+
+class PacketPacer {
+ public:
+ virtual ~PacketPacer() {}
// Returns Nothing if the slice should be dropped, or a delay if it should be
// delivered.
virtual Optional<TimeDelta> ChoosePacketDelivery(LinkState link_state,
size_t slice_size) const = 0;
-
- virtual std::string name() = 0;
-
+ virtual std::string name() const = 0;
virtual Bandwidth SimulatedBandwidth() const = 0;
+ virtual uint32_t MaximumSegmentSize() const = 0;
};
// Very fast reliable packet delivery: it's often easier to debug problems
// with HappyDelivery than with packet loss enabled (assuming it shows up).
-class HappyDelivery : public DeliverySimulator {
+class HappyDelivery : public PacketPacer {
public:
+ HappyDelivery(uint32_t mss) : mss_(mss) {}
+
Optional<TimeDelta> ChoosePacketDelivery(LinkState link_state,
size_t slice_size) const override {
return TimeDelta::FromMicroseconds(1);
}
- std::string name() override { return "HappyDelivery"; }
+ std::string name() const override {
+ return "HappyDelivery(" + std::to_string(mss_) + ")";
+ }
Bandwidth SimulatedBandwidth() const override {
return Bandwidth::FromKilobitsPerSecond(1024 * 1024);
}
+
+ virtual uint32_t MaximumSegmentSize() const override { return mss_; }
+
+ private:
+ const uint32_t mss_;
};
// Windowed number of outstanding packets
-class WindowedDelivery : public DeliverySimulator {
+class WindowedDelivery : public PacketPacer {
public:
- WindowedDelivery(int max_outstanding, TimeDelta window)
- : max_outstanding_(max_outstanding), window_(window) {}
+ WindowedDelivery(int max_outstanding, TimeDelta window, uint32_t mss)
+ : max_outstanding_(max_outstanding), window_(window), mss_(mss) {}
Optional<TimeDelta> ChoosePacketDelivery(LinkState link_state,
size_t slice_size) const override {
@@ -65,29 +89,35 @@
return window_;
}
- std::string name() override {
+ std::string name() const override {
std::ostringstream out;
- out << "WindowedDelivery{max:" << max_outstanding_ << " over " << window_
- << "}";
+ out << "WindowedDelivery(" << max_outstanding_
+ << ", TimeDelta::FromMicroseconds(" << window_.as_us() << "), " << mss_
+ << ")";
return out.str();
}
Bandwidth SimulatedBandwidth() const override {
- return Bandwidth::BytesPerTime(256 * max_outstanding_, window_);
+ return Bandwidth::BytesPerTime(MaximumSegmentSize() * max_outstanding_,
+ window_);
}
+ virtual uint32_t MaximumSegmentSize() const override { return mss_; }
+
private:
const int max_outstanding_;
const TimeDelta window_;
+ const uint32_t mss_;
};
-class InProcessLinkImpl final
+class InProcessPacketLinkImpl final
: public PacketLink,
- public std::enable_shared_from_this<InProcessLinkImpl> {
+ public std::enable_shared_from_this<InProcessPacketLinkImpl> {
public:
- InProcessLinkImpl(RouterEndpoint* src, RouterEndpoint* dest, uint64_t link_id,
- const DeliverySimulator* simulator)
- : PacketLink(src, dest->node_id(), 256, link_id),
+ InProcessPacketLinkImpl(RouterEndpoint* src, RouterEndpoint* dest,
+ uint64_t link_id, const PacketPacer* simulator)
+ : PacketLink(src, dest->node_id(), simulator->MaximumSegmentSize(),
+ link_id),
timer_(dest->timer()),
link_id_(link_id),
simulator_(simulator),
@@ -95,14 +125,14 @@
src->RegisterPeer(dest->node_id());
}
- ~InProcessLinkImpl() {
+ ~InProcessPacketLinkImpl() {
auto strong_partner = partner_.lock();
if (strong_partner != nullptr) {
strong_partner->partner_.reset();
}
}
- void Partner(std::shared_ptr<InProcessLinkImpl> other) {
+ void Partner(std::shared_ptr<InProcessPacketLinkImpl> other) {
partner_ = other;
other->partner_ = shared_from_this();
}
@@ -141,19 +171,95 @@
private:
Timer* const timer_;
const uint64_t link_id_;
- const DeliverySimulator* const simulator_;
- std::weak_ptr<InProcessLinkImpl> partner_;
+ const PacketPacer* const simulator_;
+ std::weak_ptr<InProcessPacketLinkImpl> partner_;
const NodeId from_;
int outstanding_packets_ = 0;
};
+template <class Framer>
+class InProcessStreamLinkImpl final
+ : public StreamLink,
+ public std::enable_shared_from_this<InProcessStreamLinkImpl<Framer>> {
+ public:
+ InProcessStreamLinkImpl(RouterEndpoint* src, RouterEndpoint* dest,
+ uint64_t link_id, Bandwidth bandwidth)
+ : StreamLink(src, dest->node_id(), std::make_unique<Framer>(), link_id),
+ timer_(src->timer()),
+ bandwidth_(bandwidth),
+ from_(src->node_id()) {
+ src->RegisterPeer(dest->node_id());
+ }
+
+ void Partner(std::shared_ptr<InProcessStreamLinkImpl> other) {
+ partner_ = other;
+ other->partner_ = this->shared_from_this();
+ }
+
+ void Emit(Slice slice, StatusCallback done) override {
+ OVERNET_TRACE(DEBUG) << "Emit: " << slice;
+
+ assert(!send_op_.has_value());
+ send_op_.Reset(SendOp{std::move(slice), std::move(done)});
+
+ SendNext();
+ }
+
+ private:
+ void SendNext() {
+ auto max_delivery =
+ std::max(uint64_t(1),
+ bandwidth_.BytesSentForTime(TimeDelta::FromMicroseconds(100)));
+ Slice chunk;
+ StatusCallback cb;
+ if (max_delivery >= send_op_->slice.length()) {
+ chunk = std::move(send_op_->slice);
+ cb = std::move(send_op_->done);
+ send_op_.Reset();
+ } else {
+ chunk = send_op_->slice.TakeUntilOffset(max_delivery);
+ }
+
+ auto now = timer_->Now();
+ next_send_completes_ = std::max(now, next_send_completes_) +
+ bandwidth_.SendTimeForBytes(chunk.length());
+ timer_->At(next_send_completes_, [chunk = std::move(chunk),
+ at = next_send_completes_,
+ self = this->shared_from_this()] {
+ auto strong_partner = self->partner_.lock();
+ OVERNET_TRACE(DEBUG) << (strong_partner == nullptr ? "DROP" : "EMIT")
+ << " BYTES from " << self->from_ << " " << chunk;
+ if (strong_partner) {
+ strong_partner->Process(at, chunk);
+ }
+ });
+
+ if (!cb.empty()) {
+ cb(Status::Ok());
+ } else {
+ SendNext();
+ }
+ }
+
+ Timer* const timer_;
+ std::weak_ptr<InProcessStreamLinkImpl> partner_;
+ const Bandwidth bandwidth_;
+ const NodeId from_;
+ struct SendOp {
+ Slice slice;
+ Callback<Status> done;
+ };
+ Optional<SendOp> send_op_;
+ TimeStamp next_send_completes_ = TimeStamp::Epoch();
+};
+
+template <class Impl>
class InProcessLink final : public Link {
public:
- InProcessLink(RouterEndpoint* src, RouterEndpoint* dest, uint64_t link_id,
- const DeliverySimulator* simulator)
- : impl_(new InProcessLinkImpl(src, dest, link_id, simulator)) {}
+ template <class... Arg>
+ InProcessLink(Arg&&... args) : impl_(new Impl(std::forward<Arg>(args)...)) {}
- std::shared_ptr<InProcessLinkImpl> get() { return impl_; }
+ std::shared_ptr<Impl> get() { return impl_; }
void Close(Callback<void> quiesced) override {
impl_->Close(Callback<void>(
@@ -167,37 +273,64 @@
const LinkStats* GetStats() const override { return impl_->GetStats(); }
private:
- std::shared_ptr<InProcessLinkImpl> impl_;
+ std::shared_ptr<Impl> impl_;
};
class StatsDumper final : public StatsVisitor {
public:
+ StatsDumper(const char* indent) : indent_(indent) {}
void Counter(const char* name, uint64_t value) override {
- OVERNET_TRACE(INFO) << " " << name << " = " << value;
+ OVERNET_TRACE(INFO) << indent_ << name << " = " << value;
}
+
+ private:
+ const char* const indent_;
};
+template <class T>
+void DumpStats(const char* indent, const T* stats) {
+ StatsDumper dumper(indent);
+ stats->Accept(&dumper);
+}
+
void DumpStats(const char* label, RouterEndpoint* endpoint) {
OVERNET_TRACE(INFO) << "STATS DUMP FOR: '" << label << "' -- "
<< endpoint->node_id();
endpoint->ForEachLink([endpoint](NodeId target, const Link* link) {
OVERNET_TRACE(INFO) << " LINK: " << endpoint->node_id() << "->" << target;
- StatsDumper dumper;
- link->GetStats()->Accept(&dumper);
+ DumpStats(" ", link->GetStats());
});
}
+LinkStats AccumulateLinkStats(RouterEndpoint* endpoint) {
+ LinkStats out;
+ endpoint->ForEachLink([&out](NodeId target, const Link* link) {
+ out.Merge(*link->GetStats());
+ });
+ return out;
+}
+
class Env {
public:
virtual ~Env() {}
- virtual TimeDelta AllowedTime(uint64_t length) const = 0;
-
virtual RouterEndpoint* endpoint1() = 0;
virtual RouterEndpoint* endpoint2() = 0;
+ virtual void DumpAllStats() = 0;
+
+ uint64_t OutgoingPacketsFromSource() {
+ return AccumulateLinkStats(endpoint1()).outgoing_packet_count;
+ }
+
+ uint64_t IncomingPacketsAtDestination() {
+ return AccumulateLinkStats(endpoint2()).incoming_packet_count;
+ }
+
void AwaitConnected() {
- OVERNET_TRACE(INFO) << "Test waiting for connection";
+ OVERNET_TRACE(INFO) << "Test waiting for connection between "
+ << endpoint1()->node_id() << " and "
+ << endpoint2()->node_id();
while (!endpoint1()->HasRouteTo(endpoint2()->node_id()) ||
!endpoint2()->HasRouteTo(endpoint1()->node_id())) {
endpoint1()->BlockUntilNoBackgroundUpdatesProcessing();
@@ -237,25 +370,71 @@
: Severity::INFO};
};
+class PacketLinkSimulator final : public Simulator {
+ public:
+ PacketLinkSimulator(std::unique_ptr<PacketPacer> pacer)
+ : pacer_(std::move(pacer)) {}
+
+ void MakeLinks(RouterEndpoint* ep1, RouterEndpoint* ep2, uint64_t id1,
+ uint64_t id2) const override {
+ auto link1 = MakeLink<InProcessLink<InProcessPacketLinkImpl>>(ep1, ep2, id1,
+ pacer_.get());
+ auto link2 = MakeLink<InProcessLink<InProcessPacketLinkImpl>>(ep2, ep1, id2,
+ pacer_.get());
+ link1->get()->Partner(link2->get());
+ ep1->RegisterLink(std::move(link1));
+ ep2->RegisterLink(std::move(link2));
+ }
+
+ private:
+ std::unique_ptr<PacketPacer> pacer_;
+};
+
+const char* FramerName(const ReliableFramer& framer) {
+ return "ReliableFramer";
+};
+
+const char* FramerName(const UnreliableFramer& framer) {
+ return "UnreliableFramer";
+};
+
+template <class Framer>
+class StreamLinkSimulator final : public Simulator {
+ public:
+ StreamLinkSimulator(Bandwidth bandwidth) : bandwidth_(bandwidth) {}
+
+ void MakeLinks(RouterEndpoint* ep1, RouterEndpoint* ep2, uint64_t id1,
+ uint64_t id2) const override {
+ auto link1 = MakeLink<InProcessLink<InProcessStreamLinkImpl<Framer>>>(
+ ep1, ep2, id1, bandwidth_);
+ auto link2 = MakeLink<InProcessLink<InProcessStreamLinkImpl<Framer>>>(
+ ep2, ep1, id2, bandwidth_);
+ link1->get()->Partner(link2->get());
+ ep1->RegisterLink(std::move(link1));
+ ep2->RegisterLink(std::move(link2));
+ }
+
+ private:
+ const Bandwidth bandwidth_;
+};
+
class TwoNode final : public Env {
public:
- TwoNode(const DeliverySimulator* simulator, uint64_t node_id_1,
+ TwoNode(const NamedSimulator* simulator, uint64_t node_id_1,
uint64_t node_id_2)
- : simulator_(simulator) {
+ : simulator_(simulator->simulator.get()) {
endpoint1_ = new RouterEndpoint(timer(), NodeId(node_id_1), false);
endpoint2_ = new RouterEndpoint(timer(), NodeId(node_id_2), false);
- auto link1 = MakeLink<InProcessLink>(endpoint1_, endpoint2_, 1, simulator);
- auto link2 = MakeLink<InProcessLink>(endpoint2_, endpoint1_, 2, simulator);
- link1->get()->Partner(link2->get());
- endpoint1_->RegisterLink(std::move(link1));
- endpoint2_->RegisterLink(std::move(link2));
+ simulator_->MakeLinks(endpoint1_, endpoint2_, 1, 2);
+ }
+
+ void DumpAllStats() override {
+ DumpStats("1", endpoint1_);
+ DumpStats("2", endpoint2_);
}
virtual ~TwoNode() {
if (!testing::Test::HasFailure()) {
- DumpStats("1", endpoint1_);
- DumpStats("2", endpoint2_);
-
bool done = false;
endpoint1_->Close(Callback<void>(ALLOCATED_CALLBACK, [&done, this]() {
endpoint2_->Close(Callback<void>(ALLOCATED_CALLBACK, [&done, this]() {
@@ -269,48 +448,37 @@
}
}
- TimeDelta AllowedTime(uint64_t data_length) const override {
- // TODO(ctiller): make this just
- // 'simulator_->SimulatedBandwidth().SendTimeForBytes(data_length)'
- return TimeDelta::FromSeconds(1) +
- simulator_->SimulatedBandwidth().SendTimeForBytes(3 * data_length);
- }
-
RouterEndpoint* endpoint1() override { return endpoint1_; }
RouterEndpoint* endpoint2() override { return endpoint2_; }
private:
- const DeliverySimulator* const simulator_;
+ const Simulator* const simulator_;
RouterEndpoint* endpoint1_;
RouterEndpoint* endpoint2_;
};
class ThreeNode final : public Env {
public:
- ThreeNode(const DeliverySimulator* simulator, uint64_t node_id_1,
- uint64_t node_id_2, uint64_t node_id_h)
- : simulator_(simulator) {
+ ThreeNode(const NamedSimulator* simulator_1_h,
+ const NamedSimulator* simulator_h_2, uint64_t node_id_1,
+ uint64_t node_id_h, uint64_t node_id_2)
+ : simulator_1_h_(simulator_1_h->simulator.get()),
+ simulator_h_2_(simulator_h_2->simulator.get()) {
endpoint1_ = new RouterEndpoint(timer(), NodeId(node_id_1), false);
endpointH_ = new RouterEndpoint(timer(), NodeId(node_id_h), false);
endpoint2_ = new RouterEndpoint(timer(), NodeId(node_id_2), false);
- auto link1H = MakeLink<InProcessLink>(endpoint1_, endpointH_, 1, simulator);
- auto linkH1 = MakeLink<InProcessLink>(endpointH_, endpoint1_, 2, simulator);
- auto link2H = MakeLink<InProcessLink>(endpoint2_, endpointH_, 3, simulator);
- auto linkH2 = MakeLink<InProcessLink>(endpointH_, endpoint2_, 4, simulator);
- link1H->get()->Partner(linkH1->get());
- link2H->get()->Partner(linkH2->get());
- endpoint1_->RegisterLink(std::move(link1H));
- endpoint2_->RegisterLink(std::move(link2H));
- endpointH_->RegisterLink(std::move(linkH1));
- endpointH_->RegisterLink(std::move(linkH2));
+ simulator_1_h_->MakeLinks(endpoint1_, endpointH_, 1, 2);
+ simulator_h_2_->MakeLinks(endpointH_, endpoint2_, 3, 4);
+ }
+
+ void DumpAllStats() override {
+ DumpStats("1", endpoint1_);
+ DumpStats("H", endpointH_);
+ DumpStats("2", endpoint2_);
}
virtual ~ThreeNode() {
if (!testing::Test::HasFailure()) {
- DumpStats("1", endpoint1_);
- DumpStats("H", endpointH_);
- DumpStats("2", endpoint2_);
-
bool done = false;
endpointH_->Close(Callback<void>(ALLOCATED_CALLBACK, [this, &done]() {
endpoint1_->Close(Callback<void>(ALLOCATED_CALLBACK, [this, &done]() {
@@ -327,18 +495,12 @@
}
}
- TimeDelta AllowedTime(uint64_t data_length) const override {
- // TODO(ctiller): make this just
- // 'simulator_->SimulatedBandwidth().SendTimeForBytes(data_length)'
- return TimeDelta::FromSeconds(4) +
- simulator_->SimulatedBandwidth().SendTimeForBytes(5 * data_length);
- }
-
RouterEndpoint* endpoint1() override { return endpoint1_; }
RouterEndpoint* endpoint2() override { return endpoint2_; }
private:
- const DeliverySimulator* const simulator_;
+ const Simulator* const simulator_1_h_;
+ const Simulator* const simulator_h_2_;
RouterEndpoint* endpoint1_;
RouterEndpoint* endpointH_;
RouterEndpoint* endpoint2_;
@@ -352,66 +514,6 @@
using MakeEnv = std::shared_ptr<MakeEnvInterface>;
-template <class T, class... Arg>
-MakeEnv MakeMakeEnv(const char* name, Arg&&... args) {
- class Impl final : public MakeEnvInterface {
- public:
- Impl(const char* name, std::tuple<Arg...> args)
- : name_(name), args_(args) {}
- const char* name() const { return name_.c_str(); }
- std::shared_ptr<Env> Make() const {
- return std::apply(
- [](Arg... args) { return std::make_shared<T>(args...); }, args_);
- }
-
- private:
- const std::string name_;
- const std::tuple<Arg...> args_;
- };
- return MakeEnv(
- new Impl(name, std::tuple<Arg...>(std::forward<Arg>(args)...)));
-}
-
-static const auto kSimulators = [] {
- std::vector<std::unique_ptr<DeliverySimulator>> out;
- out.emplace_back(new HappyDelivery());
- out.emplace_back(new WindowedDelivery(3, TimeDelta::FromMilliseconds(3)));
- return out;
-}();
-
-template <class Env, int N>
-void AddVariations(const char* base_name, std::vector<MakeEnv>* envs) {
- for (const auto& simulator : kSimulators) {
- std::array<int, N> ary;
- for (int i = 0; i < N; i++) {
- ary[i] = i + 1;
- }
- do {
- std::ostringstream name;
- name << base_name;
- for (auto i : ary)
- name << i;
- name << ":";
- name << simulator->name();
- envs->emplace_back(std::apply(
- [&name, simulator = simulator.get()](auto... args) {
- return MakeMakeEnv<Env>(
- name.str().c_str(),
- std::forward<DeliverySimulator* const>(simulator),
- std::forward<int>(args)...);
- },
- ary));
- } while (std::next_permutation(ary.begin(), ary.end()));
- }
-}
-
-const auto kEnvVariations = [] {
- std::vector<MakeEnv> envs;
- AddVariations<TwoNode, 2>("TwoNode", &envs);
- AddVariations<ThreeNode, 3>("ThreeNode", &envs);
- return envs;
-}();
-
class RouterEndpoint_IntegrationEnv : public ::testing::TestWithParam<MakeEnv> {
};
@@ -469,18 +571,40 @@
EXPECT_TRUE(found);
}
-INSTANTIATE_TEST_SUITE_P(RouterEndpoint_IntegrationEnv_Instance,
- RouterEndpoint_IntegrationEnv,
- ::testing::ValuesIn(kEnvVariations.begin(),
- kEnvVariations.end()));
-
struct OneMessageArgs {
MakeEnv make_env;
Slice body;
+ TimeDelta allowed_time;
+ uint64_t expected_packets;
};
std::ostream& operator<<(std::ostream& out, OneMessageArgs args) {
- return out << "env=" << args.make_env->name() << " body=" << args.body;
+ return out << args.make_env->name();
+}
+
+template <class T>
+std::string ChangeArg(std::string input, int arg, T value) {
+ std::ostringstream out;
+ for (int i = 0; i < arg - 1; i++) {
+ auto cpos = input.find(',');
+ assert(cpos != std::string::npos);
+ out << input.substr(0, cpos + 1);
+ input = input.substr(cpos + 1);
+ }
+ out << ' ' << value;
+ out << input.substr(input.find(','));
+ return out.str();
+}
+
+std::string EscapeChars(std::string chars, std::string input) {
+ std::string output;
+ for (auto c : input) {
+ if (chars.find(c) != std::string::npos) {
+ output += '\\';
+ }
+ output += c;
+ }
+ return output;
}
class RouterEndpoint_OneMessageIntegration
@@ -492,8 +616,7 @@
const std::string kService = "abc";
- const TimeDelta kAllowedTime =
- env->AllowedTime(kService.length() + GetParam().body.length());
+ const TimeDelta kAllowedTime = GetParam().allowed_time;
std::cout << "Allowed time for body: " << kAllowedTime << std::endl;
env->AwaitConnected();
@@ -524,6 +647,9 @@
EXPECT_EQ(GetParam().body, pull_text) << pull_text.AsStdString();
delete op;
*got_pull_cb = true;
+ OVERNET_TRACE(INFO) << "STATS DUMP FOR RECEIVING DATAGRAM STREAM";
+ DumpStats(" ", stream->link_stats());
+ DumpStats(" ", stream->stream_stats());
}));
});
@@ -538,28 +664,298 @@
RouterEndpoint::SendOp(stream.get(), GetParam().body.length())
.Push(GetParam().body, Callback<void>::Ignored());
+ auto start_flush = env->timer()->Now();
+
env->FlushTodo([got_pull_cb]() { return *got_pull_cb; },
- env->timer()->Now() + kAllowedTime);
+ start_flush + kAllowedTime);
+
+ auto taken_time = env->timer()->Now() - start_flush;
+ auto taken_seconds = (taken_time.as_us() + 999999) / 1000000;
+ auto allowed_seconds = kAllowedTime.as_us() / 1000000;
+ EXPECT_EQ(taken_seconds, allowed_seconds)
+ << "sed -i 's/" << GetParam().make_env->name() << "/"
+ << EscapeChars("&",
+ ChangeArg(GetParam().make_env->name(), 2, taken_seconds))
+ << "/g' " << __FILE__;
ASSERT_TRUE(*got_pull_cb);
+
+ OVERNET_TRACE(INFO) << "STATS DUMP FOR SENDING DATAGRAM STREAM";
+ DumpStats(" ", stream->link_stats());
+ DumpStats(" ", stream->stream_stats());
+
+ env->DumpAllStats();
+
+ EXPECT_EQ(env->IncomingPacketsAtDestination(), GetParam().expected_packets)
+ << "sed -i 's/" << GetParam().make_env->name() << "/"
+ << EscapeChars("&", ChangeArg(GetParam().make_env->name(), 3,
+ env->IncomingPacketsAtDestination()))
+ << "/g' " << __FILE__;
}
-const std::vector<OneMessageArgs> kOneMessageArgTests = [] {
- std::vector<OneMessageArgs> out;
- const std::vector<size_t> kLengths = {1, 32, 1024, 32768, 1048576};
- for (MakeEnv make_env : kEnvVariations) {
- for (auto payload_length : kLengths) {
- out.emplace_back(
- OneMessageArgs{make_env, Slice::RepeatedChar(payload_length, 'a')});
+template <class T, class... Arg>
+MakeEnv MakeMakeEnv(const char* name, Arg&&... args) {
+ class Impl final : public MakeEnvInterface {
+ public:
+ Impl(const char* name, std::tuple<Arg...> args)
+ : name_(name), args_(args) {}
+ const char* name() const { return name_.c_str(); }
+ std::shared_ptr<Env> Make() const {
+ return std::apply(
+ [](Arg... args) { return std::make_shared<T>(args...); }, args_);
}
- }
- return out;
-}();
-INSTANTIATE_TEST_SUITE_P(RouterEndpoint_OneMessageIntegration_Instance,
- RouterEndpoint_OneMessageIntegration,
- ::testing::ValuesIn(kOneMessageArgTests.begin(),
- kOneMessageArgTests.end()));
+ private:
+ const std::string name_;
+ const std::tuple<Arg...> args_;
+ };
+ return MakeEnv(
+ new Impl(name, std::tuple<Arg...>(std::forward<Arg>(args)...)));
+}
+
+// Simulators get abbreviations here because otherwise the environment names get
+// too difficult to communicate.
+#define DECL_SIM(name, inst) \
+ const NamedSimulator name{#name, std::unique_ptr<Simulator>(inst)};
+
+DECL_SIM(Happy, new PacketLinkSimulator(std::make_unique<HappyDelivery>(1500)));
+DECL_SIM(Win_3_3, new PacketLinkSimulator(std::make_unique<WindowedDelivery>(
+ 3, TimeDelta::FromMilliseconds(3), 256)));
+DECL_SIM(ReliableStream, new StreamLinkSimulator<ReliableFramer>(
+ Bandwidth::FromKilobitsPerSecond(1000)));
+DECL_SIM(UnreliableStream, new StreamLinkSimulator<UnreliableFramer>(
+ Bandwidth::FromKilobitsPerSecond(115)));
+
+#define MAKE_MAKE_ENV(env, ...) \
+ MakeMakeEnv<env>("MAKE_MAKE_ENV(" #env ", " #__VA_ARGS__ ")", __VA_ARGS__)
+
+INSTANTIATE_TEST_SUITE_P(
+ RouterEndpoint_IntegrationEnv_Instance, RouterEndpoint_IntegrationEnv,
+ ::testing::Values(
+ MAKE_MAKE_ENV(TwoNode, &Happy, 1, 2),
+ MAKE_MAKE_ENV(TwoNode, &Happy, 2, 1),
+ MAKE_MAKE_ENV(TwoNode, &Win_3_3, 1, 2),
+ MAKE_MAKE_ENV(TwoNode, &Win_3_3, 2, 1),
+ MAKE_MAKE_ENV(TwoNode, &ReliableStream, 1, 2),
+ MAKE_MAKE_ENV(TwoNode, &ReliableStream, 2, 1),
+ MAKE_MAKE_ENV(TwoNode, &UnreliableStream, 1, 2),
+ MAKE_MAKE_ENV(TwoNode, &UnreliableStream, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Happy, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &Win_3_3, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &ReliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Happy, &UnreliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Happy, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &Win_3_3, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &ReliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &Win_3_3, &UnreliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Happy, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &Win_3_3, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &ReliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &ReliableStream, &UnreliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Happy, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &Win_3_3, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &ReliableStream, 3, 2, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 1, 3, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 2, 1, 3),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 2, 3, 1),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 3, 1, 2),
+ MAKE_MAKE_ENV(ThreeNode, &UnreliableStream, &UnreliableStream, 3, 2,
+ 1)));
+
+#define ONE_MESSAGE_TEST(length, allowed_time, expected_packets, env, ...) \
+ OneMessageArgs { \
+ MakeMakeEnv<env>("ONE_MESSAGE_TEST(" #length ", " #allowed_time \
+ ", " #expected_packets ", " #env ", " #__VA_ARGS__ ")", \
+ __VA_ARGS__), \
+ Slice::RepeatedChar(length, 'a'), \
+ TimeDelta::FromSeconds(allowed_time), expected_packets \
+ }
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(
+ RouterEndpoint_OneMessageIntegration_Instance,
+ RouterEndpoint_OneMessageIntegration,
+ ::testing::Values(
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &Happy, 1, 2),
+ ONE_MESSAGE_TEST(32, 1, 3, TwoNode, &Happy, 1, 2),
+ ONE_MESSAGE_TEST(1024, 1, 3, TwoNode, &Happy, 1, 2),
+ ONE_MESSAGE_TEST(32768, 1, 26, TwoNode, &Happy, 1, 2),
+ ONE_MESSAGE_TEST(1048576, 1, 720, TwoNode, &Happy, 1, 2),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &Happy, 2, 1),
+ ONE_MESSAGE_TEST(1048576, 1, 720, TwoNode, &Happy, 2, 1),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &Win_3_3, 1, 2),
+ ONE_MESSAGE_TEST(32, 1, 3, TwoNode, &Win_3_3, 1, 2),
+ ONE_MESSAGE_TEST(1024, 1, 8, TwoNode, &Win_3_3, 1, 2),
+ ONE_MESSAGE_TEST(32768, 1, 155, TwoNode, &Win_3_3, 1, 2),
+ ONE_MESSAGE_TEST(1048576, 7, 4837, TwoNode, &Win_3_3, 1, 2),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &Win_3_3, 2, 1),
+ ONE_MESSAGE_TEST(1048576, 7, 4837, TwoNode, &Win_3_3, 2, 1),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &ReliableStream, 1, 2),
+ ONE_MESSAGE_TEST(32, 1, 3, TwoNode, &ReliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1024, 1, 3, TwoNode, &ReliableStream, 1, 2),
+ ONE_MESSAGE_TEST(32768, 1, 26, TwoNode, &ReliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1048576, 9, 534, TwoNode, &ReliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &ReliableStream, 2, 1),
+ ONE_MESSAGE_TEST(1048576, 9, 534, TwoNode, &ReliableStream, 2, 1),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &UnreliableStream, 1, 2),
+ ONE_MESSAGE_TEST(32, 1, 3, TwoNode, &UnreliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1024, 1, 7, TwoNode, &UnreliableStream, 1, 2),
+ ONE_MESSAGE_TEST(32768, 3, 165, TwoNode, &UnreliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1048576, 79, 4813, TwoNode, &UnreliableStream, 1, 2),
+ ONE_MESSAGE_TEST(1, 1, 3, TwoNode, &UnreliableStream, 2, 1),
+ ONE_MESSAGE_TEST(1048576, 79, 4813, TwoNode, &UnreliableStream, 2, 1),
+ ONE_MESSAGE_TEST(1, 1, 22, ThreeNode, &Happy, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 22, ThreeNode, &Happy, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 22, ThreeNode, &Happy, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 71, ThreeNode, &Happy, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 2, 791, ThreeNode, &Happy, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 24, ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 24, ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 30, ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 189, ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 6, 4977, ThreeNode, &Happy, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 18, ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 18, ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 18, ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 43, ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 9, 748, ThreeNode, &Happy, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 22, ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 22, ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 28, ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 179, ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 4861, ThreeNode, &Happy, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 24, ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 24, ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 28, ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 139, ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 6, 5082, ThreeNode, &Win_3_3, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 26, ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 26, ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 30, ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 183, ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 6, 4880, ThreeNode, &Win_3_3, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 18, ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 18, ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 23, ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 170, ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 10, 4805, ThreeNode, &Win_3_3, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 22, ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 22, ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 27, ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 178, ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 4859, ThreeNode, &Win_3_3, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 24, ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 24, ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 25, ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 67, ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 9, 1361, ThreeNode, &ReliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 18, ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 18, ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 18, ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 1, 37, ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 9, 545, ThreeNode, &ReliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 25, ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 25, ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 29, ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 179, ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 4862, ThreeNode, &ReliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 25, ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 26, ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 38, ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 251, ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 6454, ThreeNode, &UnreliableStream, &Happy, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 35, ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 35, ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 44, ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 256, ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 6473, ThreeNode, &UnreliableStream, &Win_3_3, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 23, ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 23, ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 30, ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 182, ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 4886, ThreeNode, &UnreliableStream, &ReliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1, 1, 28, ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32, 1, 28, ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1024, 1, 32, ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(32768, 3, 184, ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3),
+ ONE_MESSAGE_TEST(1048576, 84, 4884, ThreeNode, &UnreliableStream, &UnreliableStream, 1, 2, 3)));
+// clang-format on
} // namespace router_endpoint2node
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/BUILD.gn b/src/connectivity/overnet/lib/links/BUILD.gn
index 250f17b..9149c0a 100644
--- a/src/connectivity/overnet/lib/links/BUILD.gn
+++ b/src/connectivity/overnet/lib/links/BUILD.gn
@@ -22,6 +22,7 @@
":packet_link_test",
":packet_nub_test",
":stream_link_test",
+ ":packet_stuffer_test",
]
}
@@ -37,6 +38,7 @@
"//src/connectivity/overnet/lib/routing:router",
]
deps = [
+ ":packet_stuffer",
"//src/connectivity/overnet/lib/environment:trace",
"//src/connectivity/overnet/lib/packet_protocol",
]
@@ -64,7 +66,9 @@
"stream_link.h",
]
public_deps = [
+ ":packet_stuffer",
"//src/connectivity/overnet/lib/routing:router",
+ "//src/connectivity/overnet/lib/protocol:stream_framer",
]
deps = [
"//src/connectivity/overnet/lib/environment:trace",
@@ -81,6 +85,8 @@
"//src/connectivity/overnet/lib/environment:trace_cout",
"//src/connectivity/overnet/lib/testing:flags",
"//src/connectivity/overnet/lib/testing:test_timer",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
"//third_party/googletest:gmock",
"//third_party/googletest:gtest",
]
@@ -103,6 +109,8 @@
"//src/connectivity/overnet/lib/protocol:fidl",
"//src/connectivity/overnet/lib/protocol:varint",
"//src/connectivity/overnet/lib/testing:test_timer",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
]
}
@@ -117,6 +125,8 @@
"//src/connectivity/overnet/lib/protocol:fidl",
"//src/connectivity/overnet/lib/protocol:varint",
"//src/connectivity/overnet/lib/testing:test_timer",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
]
}
@@ -183,3 +193,32 @@
"//src/connectivity/overnet/lib/environment:trace_cout",
]
}
+
+# packet_stuffer
+source_set("packet_stuffer") {
+ sources = [
+ "packet_stuffer.cc",
+ "packet_stuffer.h",
+ ]
+ public_deps = [
+ "//src/connectivity/overnet/lib/routing:router",
+ ]
+ deps = [
+ "//src/connectivity/overnet/lib/environment:trace",
+ ]
+}
+
+source_set("packet_stuffer_test") {
+ testonly = true
+ sources = [
+ "packet_stuffer_test.cc",
+ ]
+ deps = [
+ ":packet_stuffer",
+ "//src/connectivity/overnet/lib/environment:trace_cout",
+ "//src/connectivity/overnet/lib/testing:flags",
+ "//src/connectivity/overnet/lib/testing:test_timer",
+ "//third_party/googletest:gmock",
+ "//third_party/googletest:gtest",
+ ]
+}
diff --git a/src/connectivity/overnet/lib/links/packet_link.cc b/src/connectivity/overnet/lib/links/packet_link.cc
index 488b2b3..c42c1bd 100644
--- a/src/connectivity/overnet/lib/links/packet_link.cc
+++ b/src/connectivity/overnet/lib/links/packet_link.cc
@@ -15,31 +15,27 @@
timer_(router->timer()),
peer_(peer),
label_(label),
- protocol_{router_->timer(), [router] { return (*router->rng())(); }, this,
- PacketProtocol::NullCodec(), mss} {}
+ protocol_{router_->timer(),
+ [router] { return (*router->rng())(); },
+ this,
+ PacketProtocol::NullCodec(),
+ mss,
+ false},
+ packet_stuffer_(router->node_id(), peer) {}
void PacketLink::Close(Callback<void> quiesced) {
ScopedModule<PacketLink> scoped_module(this);
- stashed_.Reset();
- while (!outgoing_.empty()) {
- outgoing_.pop();
- }
+ closed_ = true;
+ packet_stuffer_.DropPendingMessages();
protocol_.Close(std::move(quiesced));
}
void PacketLink::Forward(Message message) {
- // TODO(ctiller): do some real thinking about what this value should be
- constexpr size_t kMaxBufferedMessages = 32;
ScopedModule<PacketLink> scoped_module(this);
- if (outgoing_.size() >= kMaxBufferedMessages) {
- auto drop = std::move(outgoing_.front());
- outgoing_.pop();
- }
- bool send_immediately = !sending_ && outgoing_.empty();
+ const bool send_immediately =
+ packet_stuffer_.Forward(std::move(message)) && !sending_;
OVERNET_TRACE(DEBUG) << "Forward sending=" << sending_
- << " outgoing=" << outgoing_.size()
<< " imm=" << send_immediately;
- outgoing_.emplace(std::move(message));
if (send_immediately) {
SchedulePacket();
}
@@ -76,18 +72,22 @@
void PacketLink::SchedulePacket() {
assert(!sending_);
- assert(!outgoing_.empty() || stashed_.has_value());
+ assert(packet_stuffer_.HasPendingMessages());
auto r = new LinkSendRequest(this);
OVERNET_TRACE(DEBUG) << "Schedule " << r;
protocol_.Send(PacketProtocol::SendRequestHdl(r));
}
PacketLink::LinkSendRequest::LinkSendRequest(PacketLink* link) : link_(link) {
+ OVERNET_TRACE(DEBUG) << "LinkSendRequest[" << this << "]: Create";
assert(!link->sending_);
link->sending_ = true;
}
-PacketLink::LinkSendRequest::~LinkSendRequest() { assert(!blocking_sends_); }
+PacketLink::LinkSendRequest::~LinkSendRequest() {
+ assert(!blocking_sends_);
+ OVERNET_TRACE(DEBUG) << "LinkSendRequest[" << this << "]: Destroy";
+}
Slice PacketLink::LinkSendRequest::GenerateBytes(LazySliceArgs args) {
auto link = link_;
@@ -97,10 +97,10 @@
assert(blocking_sends_);
assert(link->sending_);
blocking_sends_ = false;
- auto pkt = link->BuildPacket(args);
+ auto pkt = link->packet_stuffer_.BuildPacket(args);
link->sending_ = false;
OVERNET_TRACE(DEBUG) << "LinkSendRequest[" << this << "]: Generated " << pkt;
- if (link->stashed_.has_value() || !link->outgoing_.empty()) {
+ if (link->packet_stuffer_.HasPendingMessages()) {
link->SchedulePacket();
}
return pkt;
@@ -121,93 +121,6 @@
delete this;
}
-Slice PacketLink::BuildPacket(LazySliceArgs args) {
- OVERNET_TRACE(DEBUG)
- << "Build outgoing=" << outgoing_.size() << " stashed="
- << (stashed_ ? [&](){
- std::ostringstream fmt;
- fmt << stashed_->message << "+" << stashed_->payload.length() << "b";
- return fmt.str();
- }() : "nil");
- auto remaining_length = args.max_length;
- auto add_serialized_msg = [&remaining_length, this](
- const RoutableMessage& wire,
- Slice payload) -> bool {
- auto serialized = wire.Write(router_->node_id(), peer_, std::move(payload));
- const auto serialized_length = serialized.length();
- const auto length_length = varint::WireSizeFor(serialized_length);
- const auto segment_length = length_length + serialized_length;
- OVERNET_TRACE(DEBUG) << "AddMsg segment_length=" << segment_length
- << " remaining_length=" << remaining_length
- << (segment_length > remaining_length ? " => SKIP"
- : "")
- << "; serialized:" << serialized;
- if (segment_length > remaining_length) {
- return false;
- }
- send_slices_.push_back(serialized.WithPrefix(
- length_length, [length_length, serialized_length](uint8_t* p) {
- varint::Write(serialized_length, length_length, p);
- }));
- remaining_length -= segment_length;
- return true;
- };
-
- static const uint32_t kMinMSS = 64;
- if (stashed_.has_value()) {
- if (add_serialized_msg(stashed_->message, stashed_->payload)) {
- stashed_.Reset();
- } else {
- if (args.has_other_content) {
- // Skip sending any other messages: we'll retry this message
- // without an ack momentarily.
- remaining_length = 0;
- } else {
- // There's no chance we'll ever send this message: drop it.
- abort();
- stashed_.Reset();
- OVERNET_TRACE(DEBUG) << "drop stashed";
- }
- }
- }
- while (!outgoing_.empty() && remaining_length > kMinMSS) {
- // Ensure there's space with the routing header included.
- Optional<size_t> max_len_before_prefix =
- outgoing_.front().header.MaxPayloadLength(router_->node_id(), peer_,
- remaining_length);
- if (!max_len_before_prefix.has_value() || *max_len_before_prefix <= 1) {
- break;
- }
- // And ensure there's space with the segment length header.
- auto max_len = varint::MaximumLengthWithPrefix(*max_len_before_prefix);
- // Pull out the message.
- Message msg = std::move(outgoing_.front());
- outgoing_.pop();
- // Serialize it.
- auto payload = msg.make_payload(LazySliceArgs{
- Border::None(), std::min(msg.mss, static_cast<uint32_t>(max_len)),
- args.has_other_content || !send_slices_.empty()});
- if (payload.length() == 0) {
- continue;
- }
- // Add the serialized version to the outgoing queue.
- if (!add_serialized_msg(msg.header, payload)) {
- // If it fails, stash it, and retry the next loop around.
- // This may happen if the sender is unable to trim to the maximum length.
- OVERNET_TRACE(DEBUG) << "stash too long";
- stashed_.Reset(std::move(msg.header), std::move(payload));
- break;
- }
- }
-
- Slice send =
- Slice::Join(send_slices_.begin(), send_slices_.end(),
- args.desired_border.WithAddedPrefix(SeqNum::kMaxWireLength));
- send_slices_.clear();
-
- return send;
-}
-
void PacketLink::SendPacket(SeqNum seq, LazySlice data) {
if (send_packet_queue_ != nullptr) {
send_packet_queue_->emplace(std::move(data));
@@ -228,7 +141,6 @@
seq.Write(p);
});
OVERNET_TRACE(DEBUG) << "Emit " << send_slice;
- stats_.outgoing_packet_count++;
Emit(std::move(send_slice));
if (send_packet_queue.empty()) {
@@ -243,7 +155,10 @@
}
void PacketLink::Process(TimeStamp received, Slice packet) {
- stats_.incoming_packet_count++;
+ if (closed_) {
+ return;
+ }
+
ScopedModule<PacketLink> scoped_module(this);
const uint8_t* const begin = packet.begin();
const uint8_t* p = begin;
@@ -260,8 +175,9 @@
++p;
// Packets without sequence numbers are used to end the three way handshake.
- if (p == end)
+ if (p == end) {
return;
+ }
auto seq_status = SeqNum::Parse(&p, end);
if (seq_status.is_error()) {
@@ -293,32 +209,8 @@
}
Status PacketLink::ProcessBody(TimeStamp received, Slice packet) {
- while (packet.length()) {
- const uint8_t* const begin = packet.begin();
- const uint8_t* p = begin;
- const uint8_t* const end = packet.end();
-
- uint64_t serialized_length;
- if (!varint::Read(&p, end, &serialized_length)) {
- return Status(StatusCode::INVALID_ARGUMENT,
- "Failed to parse segment length");
- }
- assert(end >= p);
- if (static_cast<uint64_t>(end - p) < serialized_length) {
- return Status(StatusCode::INVALID_ARGUMENT,
- "Message body extends past end of packet");
- }
- packet.TrimBegin(p - begin);
- auto msg_status = RoutableMessage::Parse(
- packet.TakeUntilOffset(serialized_length), router_->node_id(), peer_);
- if (msg_status.is_error()) {
- return msg_status.AsStatus();
- }
- router_->Forward(Message::SimpleForwarder(std::move(msg_status->message),
- std::move(msg_status->payload),
- received));
- }
- return Status::Ok();
+ return packet_stuffer_.ParseAndForwardTo(received, std::move(packet),
+ router_);
}
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/packet_link.h b/src/connectivity/overnet/lib/links/packet_link.h
index bccb507..5cee610 100644
--- a/src/connectivity/overnet/lib/links/packet_link.h
+++ b/src/connectivity/overnet/lib/links/packet_link.h
@@ -7,6 +7,7 @@
#include <queue>
#include "src/connectivity/overnet/lib/environment/trace.h"
+#include "src/connectivity/overnet/lib/links/packet_stuffer.h"
#include "src/connectivity/overnet/lib/packet_protocol/packet_protocol.h"
#include "src/connectivity/overnet/lib/routing/router.h"
@@ -24,13 +25,12 @@
fuchsia::overnet::protocol::LinkStatus GetLinkStatus() override final;
// Mark this link as inoperable
virtual void Tombstone();
- const LinkStats* GetStats() const override final { return &stats_; }
+ const LinkStats* GetStats() const override final { return protocol_.stats(); }
private:
void SchedulePacket();
void SendPacket(SeqNum seq, LazySlice packet) override final;
Status ProcessBody(TimeStamp received, Slice packet);
- Slice BuildPacket(LazySliceArgs args);
Router* const router_;
Timer* const timer_;
@@ -38,9 +38,8 @@
const uint64_t label_;
uint64_t metrics_version_ = 1;
PacketProtocol protocol_;
- Optional<MessageWithPayload> stashed_;
- LinkStats stats_;
bool sending_ = false;
+ bool closed_ = false;
class LinkSendRequest final : public PacketProtocol::SendRequest {
public:
@@ -56,14 +55,11 @@
bool blocking_sends_ = true;
};
- // data for a send
- std::vector<Slice> send_slices_;
-
- std::queue<Message> outgoing_;
-
// Pointer to a queue that's being used to queue pending sends.
// This queue is held on the stack within SendPacket().
std::queue<LazySlice>* send_packet_queue_ = nullptr;
+
+ PacketStuffer packet_stuffer_;
};
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/packet_nub.h b/src/connectivity/overnet/lib/links/packet_nub.h
index ec4c7086..f0257fc 100644
--- a/src/connectivity/overnet/lib/links/packet_nub.h
+++ b/src/connectivity/overnet/lib/links/packet_nub.h
@@ -14,6 +14,7 @@
#include "src/connectivity/overnet/lib/environment/timer.h"
#include "src/connectivity/overnet/lib/labels/node_id.h"
#include "src/connectivity/overnet/lib/links/packet_link.h"
+#include "src/connectivity/overnet/lib/routing/router.h"
#include "src/connectivity/overnet/lib/vocabulary/slice.h"
namespace overnet {
@@ -89,9 +90,7 @@
class NubLink final : public PacketLink {
public:
NubLink(PacketNub* nub, LinkDataPtr link, NodeId peer, uint64_t label)
- : PacketLink(nub->GetRouter(), peer, kMSS, label),
- nub_(nub),
- link_(link) {}
+ : PacketLink(nub->router_, peer, kMSS, label), nub_(nub), link_(link) {}
~NubLink() { Delist(); }
@@ -179,11 +178,13 @@
static constexpr size_t kHelloSize = 256;
static constexpr uint64_t kAnnounceResendMillis = 1000;
- PacketNub(Timer* timer, NodeId node) : timer_(timer), local_node_(node) {}
+ PacketNub(Router* router)
+ : timer_(router->timer()),
+ router_(router),
+ local_node_(router->node_id()) {}
virtual ~PacketNub() {}
virtual void SendTo(Address dest, Slice slice) = 0;
- virtual Router* GetRouter() = 0;
virtual void Publish(LinkPtr<> link) = 0;
virtual void Process(TimeStamp received, Address src, Slice slice) {
@@ -540,15 +541,16 @@
void BecomePublished(LinkDataPtr link) {
assert(link->link == nullptr);
assert(link->node_id);
- link->link = new NubLink(this, link, *link->node_id, next_label_++);
+ link->link =
+ new NubLink(this, link, *link->node_id, router_->GenerateLinkLabel());
Publish(LinkPtr<>(link->link));
}
Timer* const timer_;
+ Router* const router_;
const NodeId local_node_;
std::unordered_map<Address, LinkDataPtr, HashAddress, EqAddress> links_;
std::mt19937_64 rng_;
- uint64_t next_label_ = 1;
};
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/packet_nub_connection_fuzzer.cc b/src/connectivity/overnet/lib/links/packet_nub_connection_fuzzer.cc
index c1f7b53..3a7ebc8 100644
--- a/src/connectivity/overnet/lib/links/packet_nub_connection_fuzzer.cc
+++ b/src/connectivity/overnet/lib/links/packet_nub_connection_fuzzer.cc
@@ -16,20 +16,28 @@
class Fuzzer;
-class Nub final : public PacketNub<int, 256> {
+class NubStuff {
+ public:
+ NubStuff(Fuzzer* fuzzer, uint8_t index);
+
+ Router* router() { return &router_; }
+
+ private:
+ Router router_;
+};
+
+class Nub final : public NubStuff, 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));
+ router()->RegisterLink(std::move(link));
}
private:
Fuzzer* const fuzzer_;
const uint8_t index_;
- Router router_;
};
class Fuzzer {
@@ -38,8 +46,8 @@
Timer* timer() { return &timer_; }
bool IsDone() {
- return nub1_.GetRouter()->HasRouteTo(NodeId(2)) &&
- nub2_.GetRouter()->HasRouteTo(NodeId(1));
+ return nub1_.router()->HasRouteTo(NodeId(2)) &&
+ nub2_.router()->HasRouteTo(NodeId(1));
}
bool StepTime() { return timer_.StepUntilNextEvent(); }
bool StepTime(uint64_t us) { return timer_.Step(us); }
@@ -50,6 +58,16 @@
void Initiate1();
void Initiate2();
+ ~Fuzzer() {
+ bool done1 = false;
+ bool done2 = false;
+ nub1_.router()->Close([&done1] { done1 = true; });
+ nub2_.router()->Close([&done2] { done2 = true; });
+ while (!done1 || !done2) {
+ StepTime();
+ }
+ }
+
private:
TestTimer timer_;
TraceCout cout_{&timer_};
@@ -71,18 +89,19 @@
std::vector<Packet> packets_;
};
+NubStuff::NubStuff(Fuzzer* fuzzer, uint8_t index)
+ : router_(fuzzer->timer(), NodeId(index), false) {}
+
Nub::Nub(Fuzzer* fuzzer, uint8_t index)
- : PacketNub(fuzzer->timer(), NodeId(index)),
+ : NubStuff(fuzzer, index),
+ PacketNub(router()),
fuzzer_(fuzzer),
- index_(index),
- router_(fuzzer->timer(), NodeId(index), false) {}
+ index_(index) {}
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)); }
diff --git a/src/connectivity/overnet/lib/links/packet_nub_fuzzer.cc b/src/connectivity/overnet/lib/links/packet_nub_fuzzer.cc
index 5962617..9609397da 100644
--- a/src/connectivity/overnet/lib/links/packet_nub_fuzzer.cc
+++ b/src/connectivity/overnet/lib/links/packet_nub_fuzzer.cc
@@ -35,7 +35,7 @@
}
PacketNubFuzzer::Nub::Nub(Timer* timer)
- : BaseNub(timer, NodeId(1)), router_(timer, NodeId(1), false) {}
+ : Router(timer, NodeId(1), false), BaseNub(this) {}
void PacketNubFuzzer::Nub::Process(TimeStamp received, uint64_t src,
Slice slice) {
@@ -51,8 +51,6 @@
}
}
-Router* PacketNubFuzzer::Nub::GetRouter() { return &router_; }
-
void PacketNubFuzzer::Nub::Publish(LinkPtr<> link) {
auto node = link->GetLinkStatus().from;
if (NodeId(node) != NodeId(1)) {
diff --git a/src/connectivity/overnet/lib/links/packet_nub_fuzzer.h b/src/connectivity/overnet/lib/links/packet_nub_fuzzer.h
index cc0b527..41ce7c5 100644
--- a/src/connectivity/overnet/lib/links/packet_nub_fuzzer.h
+++ b/src/connectivity/overnet/lib/links/packet_nub_fuzzer.h
@@ -42,18 +42,16 @@
Budget budget_;
using BaseNub = PacketNub<uint64_t, 256>;
- class Nub : public BaseNub {
+ class Nub : private Router, public BaseNub {
public:
Nub(Timer* timer);
void Process(TimeStamp received, uint64_t src, Slice slice) override;
void SendTo(uint64_t dest, Slice slice) override;
- Router* GetRouter() override;
void Publish(LinkPtr<> link) override;
private:
- Router router_;
Budget budget_;
};
Nub nub_{&timer_};
diff --git a/src/connectivity/overnet/lib/links/packet_nub_test.cc b/src/connectivity/overnet/lib/links/packet_nub_test.cc
index a139f35..3d88153 100644
--- a/src/connectivity/overnet/lib/links/packet_nub_test.cc
+++ b/src/connectivity/overnet/lib/links/packet_nub_test.cc
@@ -21,10 +21,22 @@
using FakeAddress = uint32_t;
-class MockPacketNub : public PacketNub<FakeAddress, 1024> {
+class MockPacketNubBase {
+ public:
+ MockPacketNubBase(Timer* timer, NodeId node) : router_(timer, node, true) {}
+
+ Router* GetRouter() { return &router_; }
+
+ protected:
+ Router router_;
+};
+
+class MockPacketNub : public MockPacketNubBase,
+ public PacketNub<FakeAddress, 1024> {
public:
MockPacketNub(Timer* timer, NodeId node)
- : PacketNub<FakeAddress, 1024>(timer, node), router_(timer, node, true) {}
+ : MockPacketNubBase(timer, node),
+ PacketNub<FakeAddress, 1024>(GetRouter()) {}
MOCK_METHOD2(SendTo, void(FakeAddress, Slice));
MOCK_METHOD1(PublishMock, void(std::shared_ptr<LinkPtr<>>));
@@ -32,11 +44,6 @@
void Publish(LinkPtr<> link) override final {
PublishMock(std::make_shared<LinkPtr<>>(std::move(link)));
}
-
- Router* GetRouter() override final { return &router_; }
-
- private:
- Router router_;
};
TEST(PacketNub, NoOp) {
diff --git a/src/connectivity/overnet/lib/links/packet_stuffer.cc b/src/connectivity/overnet/lib/links/packet_stuffer.cc
new file mode 100644
index 0000000..8f3468b
--- /dev/null
+++ b/src/connectivity/overnet/lib/links/packet_stuffer.cc
@@ -0,0 +1,153 @@
+// 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 "src/connectivity/overnet/lib/links/packet_stuffer.h"
+
+namespace overnet {
+
+PacketStuffer::PacketStuffer(NodeId my_node_id, NodeId peer_node_id)
+ : my_node_id_(my_node_id), peer_node_id_(peer_node_id) {}
+
+bool PacketStuffer::Forward(Message message) {
+ // TODO(ctiller): do some real thinking about what this value should be
+ constexpr size_t kMaxBufferedMessages = 32;
+ if (outgoing_.size() >= kMaxBufferedMessages) {
+ auto drop = std::move(outgoing_.front());
+ outgoing_.pop();
+ }
+ const bool was_empty = !HasPendingMessages();
+ outgoing_.emplace(std::move(message));
+ return was_empty;
+}
+
+void PacketStuffer::DropPendingMessages() {
+ stashed_.Reset();
+ while (!outgoing_.empty()) {
+ outgoing_.pop();
+ }
+}
+
+bool PacketStuffer::HasPendingMessages() const {
+ return stashed_.has_value() || !outgoing_.empty();
+}
+
+Slice PacketStuffer::BuildPacket(LazySliceArgs args) {
+ OVERNET_TRACE(DEBUG)
+ << "Build outgoing=" << outgoing_.size() << " stashed="
+ << (stashed_ ? [&](){
+ std::ostringstream fmt;
+ fmt << stashed_->message << "+" << stashed_->payload.length() << "b";
+ return fmt.str();
+ }() : "nil");
+ auto remaining_length = args.max_length;
+ auto add_serialized_msg = [&remaining_length, this](
+ const RoutableMessage& wire,
+ Slice payload) -> bool {
+ auto serialized =
+ wire.Write(my_node_id_, peer_node_id_, std::move(payload));
+ const auto serialized_length = serialized.length();
+ const auto length_length = varint::WireSizeFor(serialized_length);
+ const auto segment_length = length_length + serialized_length;
+ OVERNET_TRACE(DEBUG) << "AddMsg segment_length=" << segment_length
+ << " remaining_length=" << remaining_length
+ << (segment_length > remaining_length ? " => SKIP"
+ : "")
+ << "; serialized:" << serialized;
+ if (segment_length > remaining_length) {
+ return false;
+ }
+ send_slices_.push_back(serialized.WithPrefix(
+ length_length, [length_length, serialized_length](uint8_t* p) {
+ varint::Write(serialized_length, length_length, p);
+ }));
+ remaining_length -= segment_length;
+ return true;
+ };
+
+ static const uint32_t kMinMSS = 64;
+ if (stashed_.has_value()) {
+ if (add_serialized_msg(stashed_->message, stashed_->payload)) {
+ stashed_.Reset();
+ } else {
+ if (args.has_other_content) {
+ // Skip sending any other messages: we'll retry this message
+ // without an ack momentarily.
+ remaining_length = 0;
+ } else {
+ // There's no chance we'll ever send this message: drop it.
+ abort();
+ stashed_.Reset();
+ OVERNET_TRACE(DEBUG) << "drop stashed";
+ }
+ }
+ }
+ while (!outgoing_.empty() && remaining_length > kMinMSS) {
+ // Ensure there's space with the routing header included.
+ Optional<size_t> max_len_before_prefix =
+ outgoing_.front().header.MaxPayloadLength(my_node_id_, peer_node_id_,
+ remaining_length);
+ if (!max_len_before_prefix.has_value() || *max_len_before_prefix <= 1) {
+ break;
+ }
+ // And ensure there's space with the segment length header.
+ auto max_len = varint::MaximumLengthWithPrefix(*max_len_before_prefix);
+ // Pull out the message.
+ Message msg = std::move(outgoing_.front());
+ outgoing_.pop();
+ // Serialize it.
+ auto payload = msg.make_payload(LazySliceArgs{
+ Border::None(), std::min(msg.mss, static_cast<uint32_t>(max_len)),
+ args.has_other_content || !send_slices_.empty()});
+ if (payload.length() == 0) {
+ continue;
+ }
+ // Add the serialized version to the outgoing queue.
+ if (!add_serialized_msg(msg.header, payload)) {
+ // If it fails, stash it, and retry the next loop around.
+ // This may happen if the sender is unable to trim to the maximum length.
+ OVERNET_TRACE(DEBUG) << "stash too long";
+ stashed_.Reset(std::move(msg.header), std::move(payload));
+ break;
+ }
+ }
+
+ Slice send =
+ Slice::Join(send_slices_.begin(), send_slices_.end(),
+ args.desired_border.WithAddedPrefix(SeqNum::kMaxWireLength));
+ send_slices_.clear();
+
+ return send;
+}
+
+Status PacketStuffer::ParseAndForwardTo(TimeStamp received, Slice packet,
+ Router* router) const {
+ while (packet.length()) {
+ const uint8_t* const begin = packet.begin();
+ const uint8_t* p = begin;
+ const uint8_t* const end = packet.end();
+
+ uint64_t serialized_length;
+ if (!varint::Read(&p, end, &serialized_length)) {
+ return Status(StatusCode::INVALID_ARGUMENT,
+ "Failed to parse segment length");
+ }
+ assert(end >= p);
+ if (static_cast<uint64_t>(end - p) < serialized_length) {
+ return Status(StatusCode::INVALID_ARGUMENT,
+ "Message body extends past end of packet");
+ }
+ packet.TrimBegin(p - begin);
+ auto msg_status = RoutableMessage::Parse(
+ packet.TakeUntilOffset(serialized_length), my_node_id_, peer_node_id_);
+ if (msg_status.is_error()) {
+ return msg_status.AsStatus();
+ }
+ router->Forward(Message::SimpleForwarder(std::move(msg_status->message),
+ std::move(msg_status->payload),
+ received));
+ }
+ return Status::Ok();
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/packet_stuffer.h b/src/connectivity/overnet/lib/links/packet_stuffer.h
new file mode 100644
index 0000000..2cd98222
--- /dev/null
+++ b/src/connectivity/overnet/lib/links/packet_stuffer.h
@@ -0,0 +1,41 @@
+// 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.
+
+#pragma once
+
+#include <lib/fit/function.h>
+
+#include <queue>
+
+#include "src/connectivity/overnet/lib/routing/router.h"
+
+namespace overnet {
+
+// Manages a queue of outgoing messages, packing them into packets on request.
+class PacketStuffer {
+ public:
+ PacketStuffer(NodeId my_node_id, NodeId peer_node_id);
+
+ // Forward a message.
+ // Returns true if this is the first queued message.
+ [[nodiscard]] bool Forward(Message message);
+
+ void DropPendingMessages();
+ bool HasPendingMessages() const;
+
+ Slice BuildPacket(LazySliceArgs args);
+ Status ParseAndForwardTo(TimeStamp received, Slice packet,
+ Router* router) const;
+
+ private:
+ const NodeId my_node_id_;
+ const NodeId peer_node_id_;
+ std::queue<Message> outgoing_;
+ Optional<MessageWithPayload> stashed_;
+
+ // data for a send
+ std::vector<Slice> send_slices_;
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/packet_stuffer_test.cc b/src/connectivity/overnet/lib/links/packet_stuffer_test.cc
new file mode 100644
index 0000000..575d0b7
--- /dev/null
+++ b/src/connectivity/overnet/lib/links/packet_stuffer_test.cc
@@ -0,0 +1,122 @@
+// 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 "src/connectivity/overnet/lib/links/packet_stuffer.h"
+
+#include <gtest/gtest.h>
+
+#include "src/connectivity/overnet/lib/testing/test_timer.h"
+
+namespace overnet {
+namespace packet_stuffer_test {
+
+constexpr TimeStamp kDummyTimestamp123 =
+ TimeStamp::AfterEpoch(TimeDelta::FromMicroseconds(123));
+
+Message DummyMessage() {
+ return Message{std::move(RoutableMessage(NodeId(1)).AddDestination(
+ NodeId(2), StreamId(1), SeqNum(1, 1))),
+ ForwardingPayloadFactory(Slice::FromContainer({1, 2, 3})),
+ kDummyTimestamp123};
+}
+
+template <class T>
+void IgnoreResult(T&& value) {}
+
+TEST(PacketStuffer, NoOp) { PacketStuffer stuffer(NodeId(1), NodeId(2)); }
+
+TEST(PacketStuffer, ForwardReturnValue) {
+ PacketStuffer stuffer(NodeId(1), NodeId(2));
+ EXPECT_TRUE(stuffer.Forward(DummyMessage()));
+ EXPECT_FALSE(stuffer.Forward(DummyMessage()));
+}
+
+TEST(PacketStuffer, CanDropMessages) {
+ PacketStuffer stuffer(NodeId(1), NodeId(2));
+ EXPECT_FALSE(stuffer.HasPendingMessages());
+ EXPECT_TRUE(stuffer.Forward(DummyMessage()));
+ EXPECT_TRUE(stuffer.HasPendingMessages());
+ stuffer.DropPendingMessages();
+ EXPECT_FALSE(stuffer.HasPendingMessages());
+}
+
+struct PacketVerificationArgs {
+ std::vector<Slice> messages;
+ size_t max_serialize_length;
+ Slice expected_bytes;
+};
+
+struct PacketStufferSerialization
+ : public ::testing::TestWithParam<PacketVerificationArgs> {};
+
+TEST_P(PacketStufferSerialization, Write) {
+ PacketStuffer stuffer(NodeId(1), NodeId(2));
+
+ int i = 0;
+ for (auto msg : GetParam().messages) {
+ IgnoreResult(stuffer.Forward(Message{
+ std::move(RoutableMessage(NodeId(1)).AddDestination(
+ NodeId(3), StreamId(1), SeqNum(i++, GetParam().messages.size()))),
+ ForwardingPayloadFactory(msg), kDummyTimestamp123}));
+ }
+
+ EXPECT_EQ(stuffer.BuildPacket(
+ LazySliceArgs{Border::None(), GetParam().max_serialize_length}),
+ GetParam().expected_bytes);
+}
+
+TEST_P(PacketStufferSerialization, Read) {
+ TestTimer timer;
+ Router router(&timer, NodeId(2), false);
+ std::vector<Slice> got_messages;
+
+ class MockLink final : public Link {
+ public:
+ MockLink(std::vector<Slice>* got_messages) : got_messages_(got_messages) {}
+
+ void Close(Callback<void> quiesced) override {}
+ fuchsia::overnet::protocol::LinkStatus GetLinkStatus() override {
+ return fuchsia::overnet::protocol::LinkStatus{NodeId(2).as_fidl(),
+ NodeId(3).as_fidl(), 1, 1};
+ }
+ const LinkStats* GetStats() const override { return nullptr; }
+
+ void Forward(Message message) override {
+ EXPECT_EQ(message.header.src(), NodeId(1));
+ EXPECT_EQ(message.header.destinations().size(), size_t(1));
+ EXPECT_EQ(message.header.destinations()[0].dst(), NodeId(3));
+ got_messages_->emplace_back(message.make_payload(
+ LazySliceArgs{Border::None(), std::numeric_limits<size_t>::max()}));
+ }
+
+ private:
+ std::vector<Slice>* const got_messages_;
+ };
+ router.RegisterLink(MakeLink<MockLink>(&got_messages));
+
+ while (!router.HasRouteTo(NodeId(3))) {
+ timer.StepUntilNextEvent();
+ }
+
+ auto status = PacketStuffer(NodeId(1), NodeId(2))
+ .ParseAndForwardTo(kDummyTimestamp123,
+ GetParam().expected_bytes, &router);
+
+ EXPECT_TRUE(status.is_ok()) << status;
+ EXPECT_EQ(got_messages, GetParam().messages);
+}
+
+INSTANTIATE_TEST_SUITE_P(PacketStufferSerializationSuite,
+ PacketStufferSerialization,
+ ::testing::Values(PacketVerificationArgs{
+ {Slice::FromContainer({1, 2, 3})},
+ 256,
+ Slice::FromContainer({
+ 0x16, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03,
+ })}));
+
+} // namespace packet_stuffer_test
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/stream_link.cc b/src/connectivity/overnet/lib/links/stream_link.cc
index e3cfd08..89ef723 100644
--- a/src/connectivity/overnet/lib/links/stream_link.cc
+++ b/src/connectivity/overnet/lib/links/stream_link.cc
@@ -6,92 +6,97 @@
namespace overnet {
-StreamLink::StreamLink(Router *router, NodeId peer, uint32_t mss,
- uint64_t label)
- : mss_(mss), router_(router), peer_(peer), local_id_(label) {}
+StreamLink::StreamLink(Router *router, NodeId peer,
+ std::unique_ptr<StreamFramer> framer, uint64_t label)
+ : router_(router),
+ framer_(std::move(framer)),
+ peer_(peer),
+ local_id_(label),
+ packet_stuffer_(router->node_id(), peer) {}
void StreamLink::Forward(Message message) {
- if (emitting_ || closed_) {
+ if (closed_) {
return;
}
-
- // Ensure that we fit into the mss with the routing header
- Optional<size_t> max_payload_length =
- message.header.MaxPayloadLength(router_->node_id(), peer_, mss_);
- if (!max_payload_length.has_value() || *max_payload_length <= 1) {
- // Drop packet (higher layers can resend if needed).
- return;
+ if (packet_stuffer_.Forward(std::move(message)) && !emitting_) {
+ EmitOne();
}
+}
- auto payload = message.make_payload(LazySliceArgs{
- Border::Prefix(varint::WireSizeFor(mss_)), *max_payload_length, false});
+void StreamLink::SetClosed() {
+ closed_ = true;
+ packet_stuffer_.DropPendingMessages();
+}
- auto packet =
- message.header.Write(router_->node_id(), peer_, std::move(payload));
+void StreamLink::EmitOne() {
+ static uint64_t num_forwards = 0;
+ auto n = num_forwards++;
+ OVERNET_TRACE(TRACE) << "StreamLink::EmitOne[" << n
+ << "]: forward with emitting=" << emitting_
+ << " closed=" << closed_;
- auto packet_length = packet.length();
- auto prefix_length = varint::WireSizeFor(packet_length);
+ assert(!emitting_);
+ assert(!closed_);
+ assert(packet_stuffer_.HasPendingMessages());
+
+ auto packet = packet_stuffer_.BuildPacket(
+ LazySliceArgs{framer_->desired_border, framer_->maximum_segment_size});
+
+ OVERNET_TRACE(TRACE) << "StreamLink::EmitOne[" << n << "]: emit " << packet;
emitting_ = true;
- Emit(packet.WithPrefix(
- prefix_length,
- [=](uint8_t *p) { varint::Write(packet_length, prefix_length, p); }),
- [this](const Status &status) {
+ stats_.outgoing_packet_count++;
+ Emit(framer_->Frame(std::move(packet)),
+ StatusCallback(ALLOCATED_CALLBACK, [this, n](const Status &status) {
if (status.is_error()) {
- closed_ = true;
+ OVERNET_TRACE(ERROR) << "Write failed: " << status;
+ SetClosed();
}
emitting_ = false;
- MaybeQuiesce();
- });
+ OVERNET_TRACE(TRACE) << "StreamLink::EmitOne[" << n << "]: emitted";
+ if (closed_) {
+ MaybeQuiesce();
+ } else if (packet_stuffer_.HasPendingMessages()) {
+ EmitOne();
+ }
+ }));
}
void StreamLink::Process(TimeStamp received, Slice bytes) {
+ OVERNET_TRACE(TRACE) << "StreamLink.Read: " << bytes;
+
if (closed_) {
return;
}
- buffered_input_.Append(std::move(bytes));
+ framer_->Push(std::move(bytes));
for (;;) {
- const uint8_t *begin = buffered_input_.begin();
- const uint8_t *p = begin;
- const uint8_t *end = buffered_input_.end();
-
- uint64_t segment_length;
- if (!varint::Read(&p, end, &segment_length)) {
- if (end - p >= varint::WireSizeFor(mss_)) {
- closed_ = true;
- }
+ auto input = framer_->Pop();
+ if (input.is_error()) {
+ OVERNET_TRACE(ERROR) << input.AsStatus();
+ SetClosed();
+ return;
+ }
+ if (!input->has_value()) {
return;
}
- if (segment_length > mss_) {
- closed_ = true;
+ stats_.incoming_packet_count++;
+
+ if (auto status = packet_stuffer_.ParseAndForwardTo(
+ received, std::move(**input), router_);
+ status.is_error()) {
+ OVERNET_TRACE(ERROR) << input.AsStatus();
+ SetClosed();
return;
}
-
- if (static_cast<uint64_t>(end - p) < segment_length) {
- return;
- }
-
- buffered_input_.TrimBegin(p - begin);
- auto message_with_payload =
- RoutableMessage::Parse(buffered_input_.TakeUntilOffset(segment_length),
- router_->node_id(), peer_);
-
- if (message_with_payload.is_error()) {
- closed_ = true;
- return;
- }
-
- router_->Forward(Message::SimpleForwarder(
- std::move(message_with_payload->message),
- std::move(message_with_payload->payload), received));
}
}
void StreamLink::Close(Callback<void> quiesced) {
- closed_ = true;
+ OVERNET_TRACE(DEBUG) << "Stream link closed by router";
+ SetClosed();
on_quiesced_ = std::move(quiesced);
MaybeQuiesce();
}
@@ -104,9 +109,16 @@
}
fuchsia::overnet::protocol::LinkStatus StreamLink::GetLinkStatus() {
- return fuchsia::overnet::protocol::LinkStatus{
- router_->node_id().as_fidl(), peer_.as_fidl(), local_id_, 1,
- fuchsia::overnet::protocol::LinkMetrics{}};
+ // Advertise MSS as smaller than it is to account for some bugs that exist
+ // right now.
+ // TODO(ctiller): eliminate this - we should be precise.
+ constexpr size_t kUnderadvertiseMaximumSendSize = 32;
+ fuchsia::overnet::protocol::LinkMetrics m;
+ m.set_mss(std::max(kUnderadvertiseMaximumSendSize, maximum_segment_size()) -
+ kUnderadvertiseMaximumSendSize);
+ return fuchsia::overnet::protocol::LinkStatus{router_->node_id().as_fidl(),
+ peer_.as_fidl(), local_id_, 1,
+ std::move(m)};
}
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/stream_link.h b/src/connectivity/overnet/lib/links/stream_link.h
index 3436108..5ba2774 100644
--- a/src/connectivity/overnet/lib/links/stream_link.h
+++ b/src/connectivity/overnet/lib/links/stream_link.h
@@ -4,13 +4,16 @@
#pragma once
+#include "src/connectivity/overnet/lib/links/packet_stuffer.h"
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
#include "src/connectivity/overnet/lib/routing/router.h"
namespace overnet {
class StreamLink : public Link {
public:
- StreamLink(Router* router, NodeId peer, uint32_t mss, uint64_t label);
+ StreamLink(Router* router, NodeId peer, std::unique_ptr<StreamFramer> framer,
+ uint64_t label);
void Close(Callback<void> quiesced) override final;
void Forward(Message message) override final;
@@ -20,17 +23,21 @@
void Process(TimeStamp received, Slice bytes);
virtual void Emit(Slice bytes, Callback<Status> done) = 0;
+ size_t maximum_segment_size() const { return framer_->maximum_segment_size; }
+
private:
void MaybeQuiesce();
+ void EmitOne();
+ void SetClosed();
- const size_t mss_;
Router* const router_;
+ std::unique_ptr<StreamFramer> framer_;
const NodeId peer_;
const uint64_t local_id_;
bool emitting_ = false;
bool closed_ = false;
- Slice buffered_input_;
Callback<void> on_quiesced_;
+ PacketStuffer packet_stuffer_;
LinkStats stats_;
};
diff --git a/src/connectivity/overnet/lib/links/stream_link_fuzzer.test.fidl b/src/connectivity/overnet/lib/links/stream_link_fuzzer.test.fidl
index 43ec029..1275fab 100644
--- a/src/connectivity/overnet/lib/links/stream_link_fuzzer.test.fidl
+++ b/src/connectivity/overnet/lib/links/stream_link_fuzzer.test.fidl
@@ -7,12 +7,24 @@
/// A list of packets to process on a stream link
/// For stream_link_untrusted_fuzzer
struct UntrustedInputPlan {
+ UntrustedLinkDescription link_description;
vector<vector<uint8>> input;
};
+struct UntrustedReliableLink {};
+
+struct UntrustedUnreliableLink {};
+
+/// A description of a link between nodes
+xunion UntrustedLinkDescription {
+ UntrustedReliableLink reliable;
+ UntrustedUnreliableLink unreliable;
+};
+
/// A list of actions to perform on a peer to peer fixture
/// For stream_link_peer_to_peer_fuzzer
struct PeerToPeerPlan {
+ PeerToPeerLinkDescription link_description;
vector<PeerToPeerAction> actions;
};
@@ -41,3 +53,30 @@
/// Allow this many bytes to flow from node --> the other node
uint64 allow_bytes;
};
+
+/// A reliable link: uses the ReliableFramer class
+struct PeerToPeerReliableLink {};
+
+/// An unreliable link: uses the UnreliableFramer class
+/// and additionally has a mutation plan to modify bytes on the wire
+struct PeerToPeerUnreliableLink {
+ vector<StreamMutation> mutation_plan_1_to_2;
+ vector<StreamMutation> mutation_plan_2_to_1;
+};
+
+/// A description of a link between nodes
+xunion PeerToPeerLinkDescription {
+ PeerToPeerReliableLink reliable;
+ PeerToPeerUnreliableLink unreliable;
+};
+
+/// Describes a mutation to apply to a data stream
+xunion StreamMutation {
+ /// bit[flip_bit] ^= 1
+ uint64 flip_bit;
+};
+
+struct InsertByte {
+ uint64 position;
+ uint8 byte;
+};
diff --git a/src/connectivity/overnet/lib/links/stream_link_peer_to_peer_fuzzer.cc b/src/connectivity/overnet/lib/links/stream_link_peer_to_peer_fuzzer.cc
index 75b54a8..ba6d208 100644
--- a/src/connectivity/overnet/lib/links/stream_link_peer_to_peer_fuzzer.cc
+++ b/src/connectivity/overnet/lib/links/stream_link_peer_to_peer_fuzzer.cc
@@ -3,10 +3,14 @@
// 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"
@@ -14,10 +18,54 @@
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)
- : StreamLink(router, peer, 64, 1), timer_(router->timer()) {}
+ 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(); }
@@ -38,11 +86,13 @@
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));
+ 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; }
@@ -52,6 +102,8 @@
Slice pending_;
Callback<Status> done_;
FuzzedStreamLink* partner_ = nullptr;
+ uint64_t offset_ = 0;
+ StreamMutator mutator_;
};
class FuzzedHandler final : public Router::StreamHandler {
@@ -82,13 +134,16 @@
class StreamLinkFuzzer {
public:
- StreamLinkFuzzer(bool log_stuff)
+ 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));
+ 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));
+ 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));
@@ -218,13 +273,43 @@
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()) {
- StreamLinkFuzzer(false).Run(std::move(*buffer));
+ 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;
}
diff --git a/src/connectivity/overnet/lib/links/stream_link_test.cc b/src/connectivity/overnet/lib/links/stream_link_test.cc
index bfde81f..a6d7067 100644
--- a/src/connectivity/overnet/lib/links/stream_link_test.cc
+++ b/src/connectivity/overnet/lib/links/stream_link_test.cc
@@ -3,9 +3,13 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/links/stream_link.h"
+
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
+#include "src/connectivity/overnet/lib/protocol/reliable_framer.h"
+#include "src/connectivity/overnet/lib/protocol/unreliable_framer.h"
#include "src/connectivity/overnet/lib/testing/flags.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
@@ -18,8 +22,6 @@
namespace overnet {
namespace stream_link_test {
-constexpr auto kTestMSS = 2048;
-
class MockStreamHandler : public Router::StreamHandler {
public:
MOCK_METHOD3(HandleMessage, void(SeqNum, TimeStamp, Slice));
@@ -30,13 +32,15 @@
public:
MOCK_METHOD2(Emit, void(Slice, std::function<void(Status)>));
- LinkPtr<StreamLink> MakeLink(Router* router, NodeId peer, uint32_t mss) {
+ LinkPtr<StreamLink> MakeLink(Router* router, NodeId peer,
+ std::unique_ptr<StreamFramer> framer) {
static uint64_t next_label = 1;
class PacketLinkImpl final : public StreamLink {
public:
PacketLinkImpl(MockStreamLink* mock, Router* router, NodeId peer,
- uint32_t mss)
- : StreamLink(router, peer, mss, next_label++), mock_(mock) {}
+ std::unique_ptr<StreamFramer> framer)
+ : StreamLink(router, peer, std::move(framer), next_label++),
+ mock_(mock) {}
void Emit(Slice packet, Callback<Status> done) {
auto cb = std::make_shared<Callback<Status>>(std::move(done));
mock_->Emit(std::move(packet),
@@ -46,7 +50,8 @@
private:
MockStreamLink* mock_;
};
- return overnet::MakeLink<PacketLinkImpl>(this, router, peer, mss);
+ return overnet::MakeLink<PacketLinkImpl>(this, router, peer,
+ std::move(framer));
}
};
@@ -62,15 +67,15 @@
: Severity::INFO};
};
-TEST(StreamLink, NoOp) {
- TestEnvironment env;
+struct TestArg {
+ std::function<std::unique_ptr<StreamFramer>()> make_framer;
+ Slice payload;
+ Slice framed;
+};
- StrictMock<MockStreamLink> mock_link;
- Router router(env.timer(), NodeId(1), true);
- router.RegisterLink(mock_link.MakeLink(&router, NodeId(2), kTestMSS));
-}
+struct StreamLinkTest : public ::testing::TestWithParam<TestArg> {};
-TEST(StreamLink, SendOne) {
+TEST_P(StreamLinkTest, SendOne) {
TestEnvironment env;
StrictMock<MockStreamLink> mock_link;
@@ -79,7 +84,8 @@
};
Router router(env.timer(), NodeId(1), true);
- router.RegisterLink(mock_link.MakeLink(&router, NodeId(2), kTestMSS));
+ router.RegisterLink(
+ mock_link.MakeLink(&router, NodeId(2), GetParam().make_framer()));
while (!router.HasRouteTo(NodeId(2))) {
router.BlockUntilNoBackgroundUpdatesProcessing();
env.timer()->StepUntilNextEvent();
@@ -92,23 +98,24 @@
router.Forward(Message{
std::move(RoutableMessage(NodeId(1)).AddDestination(
NodeId(2), StreamId(1), SeqNum(1, 1))),
- ForwardingPayloadFactory(Slice::FromContainer({7, 8, 9})),
+ ForwardingPayloadFactory(Slice::FromContainer(GetParam().payload)),
env.timer()->Now(),
});
verify_all();
done_emit(Status::Ok());
- EXPECT_EQ(Slice::FromContainer({6, 0, 1, 1, 7, 8, 9}), emitted);
+ EXPECT_EQ(GetParam().framed, emitted);
}
-TEST(StreamLink, RecvOne) {
+TEST_P(StreamLinkTest, RecvOne) {
TestEnvironment env;
StrictMock<MockStreamLink> mock_link;
StrictMock<MockStreamHandler> mock_stream_handler;
Router router(env.timer(), NodeId(2), true);
- auto link_unique = mock_link.MakeLink(&router, NodeId(1), kTestMSS);
+ auto link_unique =
+ mock_link.MakeLink(&router, NodeId(1), GetParam().make_framer());
auto* link = link_unique.get();
router.RegisterLink(std::move(link_unique));
while (!router.HasRouteTo(NodeId(2))) {
@@ -120,9 +127,19 @@
.is_ok());
EXPECT_CALL(mock_stream_handler, HandleMessage(_, _, _));
- link->Process(env.timer()->Now(),
- Slice::FromContainer({6, 0, 1, 1, 7, 8, 9}));
+ link->Process(env.timer()->Now(), GetParam().framed);
}
+INSTANTIATE_TEST_SUITE_P(
+ StreamLinkSuite, StreamLinkTest,
+ ::testing::Values(
+ TestArg{[] { return std::make_unique<ReliableFramer>(); },
+ Slice::FromContainer({7, 8, 9}),
+ Slice::FromContainer({0x06, 0x00, 6, 0, 1, 1, 7, 8, 9})},
+ TestArg{[] { return std::make_unique<UnreliableFramer>(); },
+ Slice::FromContainer({7, 8, 9}),
+ Slice::FromContainer({0x0a, 6, 6, 0, 1, 1, 7, 8, 9, 0xb8, 0x80,
+ 0x2a, 0xcf})}));
+
} // namespace stream_link_test
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/links/stream_link_untrusted_fuzzer.cc b/src/connectivity/overnet/lib/links/stream_link_untrusted_fuzzer.cc
index 75de983..2696717 100644
--- a/src/connectivity/overnet/lib/links/stream_link_untrusted_fuzzer.cc
+++ b/src/connectivity/overnet/lib/links/stream_link_untrusted_fuzzer.cc
@@ -3,9 +3,12 @@
// found in the LICENSE file.
#include <fuchsia/overnet/streamlinkfuzzer/cpp/fidl.h>
+
#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"
@@ -15,21 +18,22 @@
class FuzzedStreamLink final : public StreamLink {
public:
- FuzzedStreamLink(Router* router) : StreamLink(router, NodeId(1), 64, 1) {}
+ FuzzedStreamLink(Router* router, std::unique_ptr<StreamFramer> framer)
+ : StreamLink(router, NodeId(1), std::move(framer), 1) {}
void Emit(Slice slice, Callback<Status> done) override { abort(); }
};
class StreamLinkFuzzer {
public:
- StreamLinkFuzzer(bool log_stuff)
+ StreamLinkFuzzer(bool log_stuff, std::unique_ptr<StreamFramer> framer)
: logging_(log_stuff ? new Logging(&timer_) : nullptr) {
- auto link = MakeLink<FuzzedStreamLink>(&router_);
+ auto link = MakeLink<FuzzedStreamLink>(&router_, std::move(framer));
link_ = link.get();
router_.RegisterLink(std::move(link));
}
- void Run(fuchsia::overnet::streamlinkfuzzer::UntrustedInputPlan plan) {
- for (const auto& action : plan.input) {
+ void Run(std::vector<std::vector<uint8_t>> plan) {
+ for (const auto& action : plan) {
link_->Process(timer_.Now(), Slice::FromContainer(action));
timer_.Step(TimeDelta::FromSeconds(1).as_us());
}
@@ -47,6 +51,21 @@
FuzzedStreamLink* link_;
};
+std::unique_ptr<StreamFramer> MakeFramer(
+ const fuchsia::overnet::streamlinkfuzzer::UntrustedLinkDescription& desc) {
+ switch (desc.Which()) {
+ case fuchsia::overnet::streamlinkfuzzer::UntrustedLinkDescription::Tag::
+ Empty:
+ return nullptr;
+ case fuchsia::overnet::streamlinkfuzzer::UntrustedLinkDescription::Tag::
+ kReliable:
+ return std::make_unique<ReliableFramer>();
+ case fuchsia::overnet::streamlinkfuzzer::UntrustedLinkDescription::Tag::
+ kUnreliable:
+ return std::make_unique<UnreliableFramer>();
+ }
+}
+
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
@@ -54,7 +73,9 @@
Decode<fuchsia::overnet::streamlinkfuzzer::UntrustedInputPlan>(
Slice::FromCopiedBuffer(data, size));
buffer.is_ok()) {
- StreamLinkFuzzer(false).Run(std::move(*buffer));
+ if (auto framer = MakeFramer(buffer->link_description)) {
+ StreamLinkFuzzer(false, std::move(framer)).Run(std::move(buffer->input));
+ }
}
return 0;
}
diff --git a/src/connectivity/overnet/lib/packet_protocol/BUILD.gn b/src/connectivity/overnet/lib/packet_protocol/BUILD.gn
index a74bb48..19edcfe 100644
--- a/src/connectivity/overnet/lib/packet_protocol/BUILD.gn
+++ b/src/connectivity/overnet/lib/packet_protocol/BUILD.gn
@@ -29,8 +29,6 @@
]
public_deps = [
"//third_party/boringssl:crypto",
- ]
- deps = [
":packet_protocol",
"//src/connectivity/overnet/lib/protocol:serialization_helpers",
]
@@ -87,7 +85,7 @@
"packet_protocol.cc",
"packet_protocol.h",
]
- deps = [
+ public_deps = [
":bbr",
"//src/connectivity/overnet/lib/environment:timer",
"//src/connectivity/overnet/lib/environment:trace",
@@ -100,6 +98,8 @@
"//src/connectivity/overnet/lib/vocabulary:optional",
"//src/connectivity/overnet/lib/vocabulary:slice",
"//src/connectivity/overnet/lib/vocabulary:status",
+ "//src/connectivity/overnet/lib/stats:link",
+ "//zircon/public/lib/fit",
]
}
diff --git a/src/connectivity/overnet/lib/packet_protocol/bbr.h b/src/connectivity/overnet/lib/packet_protocol/bbr.h
index 0f4b383..1218a182 100644
--- a/src/connectivity/overnet/lib/packet_protocol/bbr.h
+++ b/src/connectivity/overnet/lib/packet_protocol/bbr.h
@@ -78,6 +78,10 @@
TimeDelta rtt() const { return rtprop_; }
+ uint64_t bandwidth_delay_product() const {
+ return bottleneck_bandwidth().BytesSentForTime(rtt());
+ }
+
// Reporter should have a Put(name, value) method.
// ... much like CsvWriter, but we don't include that here so that we can keep
// that code testonly
diff --git a/src/connectivity/overnet/lib/packet_protocol/bdp_estimator.h b/src/connectivity/overnet/lib/packet_protocol/bdp_estimator.h
new file mode 100644
index 0000000..48daa0b
--- /dev/null
+++ b/src/connectivity/overnet/lib/packet_protocol/bdp_estimator.h
@@ -0,0 +1,39 @@
+// 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.
+
+#pragma once
+
+#include <cstdint>
+
+#include "src/connectivity/overnet/lib/packet_protocol/windowed_filter.h"
+
+namespace overnet {
+
+class BdpEstimator {
+ public:
+ BdpEstimator() : filter_(1024, 0, 0) {}
+
+ struct PerPacketData {
+ uint64_t seq;
+ uint64_t bytes_received_at_send;
+ };
+
+ void ReceivedBytes(uint64_t count) { bytes_received_ += count; }
+
+ PerPacketData SentPacket(uint64_t seq) {
+ return PerPacketData{seq, bytes_received_};
+ }
+
+ void AckPacket(PerPacketData data) {
+ filter_.Update(data.seq, bytes_received_ - data.bytes_received_at_send);
+ }
+
+ uint64_t estimate() const { return filter_.best_estimate(); }
+
+ private:
+ uint64_t bytes_received_ = 0;
+ WindowedFilter<uint64_t, uint64_t, MaxFilter> filter_;
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/packet_protocol/packet_protocol.cc b/src/connectivity/overnet/lib/packet_protocol/packet_protocol.cc
index 0515834..80fc2c0 100644
--- a/src/connectivity/overnet/lib/packet_protocol/packet_protocol.cc
+++ b/src/connectivity/overnet/lib/packet_protocol/packet_protocol.cc
@@ -5,6 +5,7 @@
#include "src/connectivity/overnet/lib/packet_protocol/packet_protocol.h"
#include <iostream>
+#include <sstream>
namespace overnet {
@@ -26,10 +27,11 @@
PacketProtocol::PacketProtocol(Timer* timer, RandFunc rand,
PacketSender* packet_sender, const Codec* codec,
- uint64_t mss)
+ uint64_t mss, bool probe_tails)
: codec_(codec),
timer_(timer),
packet_sender_(packet_sender),
+ probe_tails_(probe_tails),
maximum_send_size_(mss) {
state_.Reset(this, std::move(rand));
}
@@ -53,42 +55,45 @@
ScopedModule<PacketProtocol> in_pp(this);
OVERNET_TRACE(DEBUG) << "Quiesce";
state_.Reset();
- master_ref_.Drop();
+ primary_ref_.Drop();
}
PacketProtocol::Transaction::Transaction(PacketProtocol* protocol)
: protocol_(protocol) {
+ ScopedModule<PacketProtocol> in_pp(protocol_);
+ OVERNET_TRACE(DEBUG) << "Transaction.Begin";
assert(protocol_->active_transaction_ == nullptr);
protocol_->active_transaction_ = this;
}
PacketProtocol::Transaction::~Transaction() {
+ OVERNET_TRACE(DEBUG) << "Transaction.Finalize";
+ ProtocolRef protocol(protocol_);
for (;;) {
if (quiesce_) {
- assert(protocol_->active_transaction_ == this);
- protocol_->active_transaction_ = nullptr;
- protocol_->Quiesce();
- return;
+ protocol->Quiesce();
+ } else if (protocol->state_.has_value()) {
+ if (bbr_ack_.has_value()) {
+ protocol->state_->bbr.OnAck(bbr_ack_.Take());
+ continue;
+ } else if (start_sending_) {
+ start_sending_ = false;
+ protocol->state_->outstanding_messages.StartSending();
+ continue;
+ } else if (increment_outstanding_tip_) {
+ increment_outstanding_tip_ = false;
+ protocol->state_->outstanding_messages.IncrementTip();
+ continue;
+ }
}
- if (schedule_send_queue_ && protocol_->state_.has_value()) {
- schedule_send_queue_ = false;
- protocol_->state_->send_queue->Schedule();
- continue;
- }
-
- assert(protocol_->active_transaction_ == this);
- protocol_->active_transaction_ = nullptr;
+ OVERNET_TRACE(DEBUG) << "Transaction.End";
+ assert(protocol->active_transaction_ == this);
+ protocol->active_transaction_ = nullptr;
return;
}
}
-void PacketProtocol::Transaction::Send(SendRequestHdl hdl) {
- if (auto* q = send_queue()) {
- schedule_send_queue_ |= q->Add(std::move(hdl));
- }
-}
-
void PacketProtocol::Transaction::QuiesceOnCompletion(Callback<void> callback) {
OVERNET_TRACE(DEBUG) << "Schedule Quiesce";
assert(!quiesce_);
@@ -97,16 +102,6 @@
quiesce_ = true;
}
-PacketProtocol::SendQueue* PacketProtocol::Transaction::send_queue() {
- if (quiesce_ || !protocol_->state_.has_value()) {
- return nullptr;
- }
- if (!protocol_->state_->send_queue.has_value()) {
- protocol_->state_->send_queue.Reset(protocol_);
- }
- return protocol_->state_->send_queue.get();
-}
-
///////////////////////////////////////////////////////////////////////////////
// PacketProtocol::Send and friends.
// Defines the send path.
@@ -115,132 +110,177 @@
ScopedModule<PacketProtocol> in_pp(this);
OVERNET_TRACE(DEBUG) << "Send";
InTransaction([&](Transaction* transaction) {
- transaction->Send(std::move(send_request));
+ if (!state_.has_value()) {
+ return;
+ }
+ state_->outstanding_messages.Schedule(transaction, std::move(send_request));
});
}
-bool PacketProtocol::SendQueue::Add(SendRequestHdl hdl) {
- ScopedModule<PacketProtocol> in_pp(protocol_);
- OVERNET_TRACE(DEBUG) << "SendQueue.Add";
- scheduled_tail_loss_probe_.Reset();
- requests_.push(std::move(hdl));
- return !scheduled_;
+void PacketProtocol::OutstandingMessages::Schedule(Transaction* transaction,
+ SendRequestHdl hdl) {
+ Schedule(transaction, OutstandingPacket::Pending{std::move(hdl)});
}
-void PacketProtocol::SendQueue::Schedule() {
+std::string PacketProtocol::OutstandingMessages::OutstandingString() const {
+ std::ostringstream out;
+ out << "{tip=" << send_tip_ << ":" << unsent_tip_ << "|";
+ bool first = true;
+ for (const auto& pkt : outstanding_) {
+ if (!first) {
+ out << ",";
+ }
+ first = false;
+ out << pkt.state;
+ }
+ out << "}";
+ return out.str();
+}
+
+void PacketProtocol::OutstandingMessages::Schedule(
+ Transaction* transaction,
+ OutstandingPacket::State outstanding_packet_state) {
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.Schedule "
+ << outstanding_packet_state
+ << " outstanding=" << OutstandingString();
+ tail_probe_timeout_.Reset();
+ auto make_packet = [&] {
+ return OutstandingPacket{
+ protocol_->timer_->Now(),
+ protocol_->state_->received_queue.max_seen_sequence(),
+ std::move(outstanding_packet_state)};
+ };
+ if (!outstanding_.empty() &&
+ std::holds_alternative<OutstandingPacket::PendingTailProbe>(
+ outstanding_.back().state)) {
+ if (!std::holds_alternative<OutstandingPacket::PendingTailProbe>(
+ outstanding_packet_state)) {
+ outstanding_.back() = make_packet();
+ }
+ } else {
+ if (unsent_tip_ - send_tip_ == outstanding_.size()) {
+ transaction->StartSendingOnCompletion();
+ }
+ outstanding_.emplace_back(make_packet());
+ }
+ ScheduleRetransmit();
+ max_outstanding_size_ =
+ std::max(outstanding_.size(), size_t(max_outstanding_size_));
+}
+
+void PacketProtocol::OutstandingMessages::StartSending() {
ScopedModule<PacketProtocol> in_pp(protocol_);
- OVERNET_TRACE(DEBUG) << "SendQueue.Schedule";
- assert(!scheduled_);
- scheduled_ = true;
- assert(!transmit_request_.has_value());
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.StartSending: "
+ << OutstandingString();
+
+ assert(!outstanding_.empty());
+ assert(unsent_tip_ >= send_tip_);
+ assert(unsent_tip_ - send_tip_ < outstanding_.size());
+
transmit_request_.Reset(
- &protocol_->state_->bbr_, [this](const Status& status) {
- ScopedModule<PacketProtocol> scoped_module(protocol_);
- OVERNET_TRACE(DEBUG) << "SendQueue.Schedule --> " << status
- << " requests=" << requests_.size();
- auto transmit_request = transmit_request_.Take();
+ &protocol_->state_->bbr, [this](const Status& status) {
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.Send status=" << status;
if (status.is_error()) {
return;
}
- SendRequestHdl request;
- if (requests_.empty()) {
- last_send_was_tail_loss_probe_ = true;
- protocol_->state_->outstanding_messages.Send(
- std::move(transmit_request), SendRequestHdl(&ack_send_request));
- } else {
- last_send_was_tail_loss_probe_ = false;
- request = std::move(requests_.front());
- requests_.pop();
- protocol_->state_->outstanding_messages.Send(
- std::move(transmit_request), std::move(request));
- }
+ SeqNum seq_num(unsent_tip_, max_outstanding_size_ + 1);
+ protocol_->packet_sender_->SendPacket(
+ seq_num, PacketSend(protocol_, transmit_request_.Take()));
});
}
-void PacketProtocol::OutstandingMessages::Send(BBR::TransmitRequest bbr_request,
- SendRequestHdl request) {
- assert(protocol_->state_);
-
- const uint64_t seq_idx = send_tip_ + outstanding_.size();
- OVERNET_TRACE(DEBUG) << "OutstandingMessages.Send seq_idx=" << seq_idx;
- SeqNum seq_num(seq_idx, max_outstanding_size_ + 1);
-
- outstanding_.emplace_back(
- OutstandingPacket{OutstandingPacketState::PENDING,
- protocol_->state_->received_queue.max_seen_sequence(),
- Nothing, std::move(request)});
-
- max_outstanding_size_ =
- std::max(outstanding_.size(), size_t(max_outstanding_size_));
-
- protocol_->packet_sender_->SendPacket(
- seq_num, PacketSend(protocol_, seq_idx, std::move(bbr_request)));
-}
-
PacketProtocol::PacketSend::PacketSend(PacketProtocol* protocol,
- uint64_t seq_idx,
BBR::TransmitRequest bbr_request)
- : protocol_(protocol),
- seq_idx_(seq_idx),
- bbr_request_(std::move(bbr_request)) {
+ : protocol_(protocol), bbr_request_(std::move(bbr_request)) {
assert(protocol);
}
Slice PacketProtocol::PacketSend::operator()(LazySliceArgs args) {
auto protocol = std::move(protocol_);
- auto slice =
- protocol->InTransaction([=, protocol = protocol.get()](Transaction* t) {
+ protocol->stats_.outgoing_packet_count++;
+ return protocol->InTransaction(
+ [=, protocol = protocol.get()](Transaction* transaction) {
ScopedModule<PacketProtocol> in_pp(protocol);
+ Slice output;
if (protocol->state_.has_value()) {
- return protocol->state_->outstanding_messages.GeneratePacket(
- std::move(bbr_request_), seq_idx_, args);
- } else {
- return Slice();
+ output = protocol->state_->outstanding_messages.GeneratePacket(
+ transaction, std::move(bbr_request_), args);
}
+ if (protocol->state_.has_value()) {
+ protocol->state_->outstanding_messages.SentMessage(transaction);
+ }
+ return output;
});
- if (protocol->state_.has_value()) {
- protocol->state_->send_queue->SentMessage();
- }
- return slice;
}
PacketProtocol::PacketSend::~PacketSend() {
if (protocol_.has_value() && protocol_->state_.has_value()) {
- protocol_->state_->outstanding_messages.CancelPacket(seq_idx_);
+ protocol_->InTransaction([this](Transaction* transaction) {
+ protocol_->state_->outstanding_messages.CancelledMessage(transaction);
+ });
}
}
-void PacketProtocol::OutstandingMessages::CancelPacket(uint64_t seq_idx) {
- protocol_->state_->send_queue->SentMessage();
- AckProcessor(this, TimeDelta::Zero()).Nack(seq_idx, Status::Cancelled());
+void PacketProtocol::OutstandingMessages::CancelledMessage(
+ Transaction* transaction) {
+ SentMessage(transaction);
}
Slice PacketProtocol::OutstandingMessages::GeneratePacket(
- BBR::TransmitRequest bbr_request, uint64_t seq_idx, LazySliceArgs args) {
- if (seq_idx < send_tip_) {
- // Frame was nacked before sending (probably due to shutdown).
- OVERNET_TRACE(DEBUG) << "Seq " << seq_idx << " nacked before sending";
+ Transaction* transaction, BBR::TransmitRequest bbr_request,
+ LazySliceArgs args) {
+ if (unsent_tip_ - send_tip_ == outstanding_.size()) {
+ // nack before send?
+ OVERNET_TRACE(DEBUG) << "Seq " << unsent_tip_ << " nacked before sending";
return Slice();
}
+ const uint64_t seq_idx = unsent_tip_;
auto* outstanding_packet = &outstanding_[seq_idx - send_tip_];
- auto send = protocol_->codec_->Encode(
- seq_idx, protocol_->FormatPacket(
- seq_idx, outstanding_packet->request.borrow(), args));
- if (send.is_error()) {
- OVERNET_TRACE(ERROR) << "Failed to encode packet: " << send.AsStatus();
+
+ OVERNET_TRACE(DEBUG) << "GeneratePacket: " << outstanding_packet->state;
+
+ SendRequestHdl request;
+ Slice send;
+
+ if (std::holds_alternative<OutstandingPacket::Pending>(
+ outstanding_packet->state)) {
+ auto& pending_packet =
+ std::get<OutstandingPacket::Pending>(outstanding_packet->state);
+ request = std::move(pending_packet.request);
+ last_send_was_tail_probe_ = false;
+ } else if (std::holds_alternative<OutstandingPacket::PendingTailProbe>(
+ outstanding_packet->state)) {
+ request = SendRequestHdl(&ack_send_request);
+ last_send_was_tail_probe_ = true;
+ } else {
+ abort();
}
+
+ auto send_status = protocol_->codec_->Encode(
+ seq_idx,
+ protocol_->FormatPacket(transaction, seq_idx, request.borrow(), args));
+ if (send_status.is_error()) {
+ OVERNET_TRACE(ERROR) << "Failed to encode packet: "
+ << send_status.AsStatus();
+ } else {
+ send = std::move(*send_status);
+ }
+
// outstanding_ should not have changed
+ assert(unsent_tip_ == seq_idx);
assert(outstanding_packet == &outstanding_[seq_idx - send_tip_]);
- outstanding_packet->state = OutstandingPacketState::SENT;
- outstanding_packet->bbr_sent_packet =
- bbr_request.Sent(BBR::OutgoingPacket{seq_idx, send->length()});
+ outstanding_packet->state = OutstandingPacket::Sent{
+ std::move(request),
+ bbr_request.Sent(BBR::OutgoingPacket{seq_idx, send.length()}),
+ protocol_->state_->bdp_estimator.SentPacket(seq_idx)};
+ unsent_tip_++;
ScheduleRetransmit();
- return std::move(*send);
+ return send;
}
-Slice PacketProtocol::FormatPacket(uint64_t seq_idx, SendRequest* request,
- LazySliceArgs args) {
+Slice PacketProtocol::FormatPacket(Transaction* transaction, uint64_t seq_idx,
+ SendRequest* request, LazySliceArgs args) {
assert(state_.has_value());
const auto max_length = std::min(static_cast<uint64_t>(args.max_length),
@@ -255,21 +295,29 @@
maxlen, has_other_content || args.has_other_content};
};
+ OVERNET_TRACE(DEBUG) << "FormatPacket: can_build_ack="
+ << state_->received_queue.CanBuildAck()
+ << " request.must_send_ack=" << request->must_send_ack()
+ << " should_send_ack="
+ << state_->ack_sender.ShouldSendAck();
+
if (state_->received_queue.CanBuildAck() &&
(request->must_send_ack() || state_->ack_sender.ShouldSendAck())) {
+ stats_.acks_sent++;
auto ack = state_->received_queue.BuildAck(
- seq_idx, timer_->Now(), varint::MaximumLengthWithPrefix(max_length),
- &state_->ack_sender);
+ transaction, seq_idx, timer_->Now(),
+ varint::MaximumLengthWithPrefix(max_length), &state_->ack_sender);
AckFrame::Writer ack_writer(&ack);
const uint8_t ack_length_length =
varint::WireSizeFor(ack_writer.wire_length());
const uint64_t prefix_length = ack_length_length + ack_writer.wire_length();
- return request->GenerateBytes(make_args(prefix_length, true))
- .WithPrefix(prefix_length,
- [&ack_writer, ack_length_length](uint8_t* p) {
- ack_writer.Write(varint::Write(ack_writer.wire_length(),
- ack_length_length, p));
- });
+ auto payload = request->GenerateBytes(make_args(prefix_length, true));
+ stats_.pure_acks_sent += payload.length() == 0;
+ return std::move(payload).WithPrefix(
+ prefix_length, [&ack_writer, ack_length_length](uint8_t* p) {
+ ack_writer.Write(
+ varint::Write(ack_writer.wire_length(), ack_length_length, p));
+ });
} else {
return request->GenerateBytes(make_args(1, false))
.WithPrefix(1, [](uint8_t* p) { *p = 0; });
@@ -287,7 +335,8 @@
return received_tip_ + idx;
}
-AckFrame PacketProtocol::ReceivedQueue::BuildAck(uint64_t seq_idx,
+AckFrame PacketProtocol::ReceivedQueue::BuildAck(Transaction* transaction,
+ uint64_t seq_idx,
TimeStamp now,
uint32_t max_length,
AckSender* ack_sender) {
@@ -311,18 +360,21 @@
OVERNET_TRACE(DEBUG)
<< "Mark unseen packet " << seq_idx << " as NOT_RECEIVED";
received_packet = ReceivedPacket{ReceiveState::NOT_RECEIVED, now};
+ stats_->unseen_packets_marked_not_received++;
[[fallthrough]];
case ReceiveState::NOT_RECEIVED:
ack.AddNack(seq_idx);
break;
case ReceiveState::RECEIVED:
+ case ReceiveState::RECEIVED_PURE_ACK:
+ case ReceiveState::RECEIVED_AND_ACKED_IMMEDIATELY:
break;
}
}
ack.AdjustForMSS(max_length, packet_delay);
- ack_sender->AckSent(seq_idx, ack.partial());
+ ack_sender->AckSent(transaction, seq_idx, ack.partial());
OVERNET_TRACE(DEBUG) << "BuildAck generates: " << ack << " bytes="
<< Slice::FromWriters(AckFrame::Writer(&ack));
@@ -330,62 +382,53 @@
return ack;
}
-void PacketProtocol::SendQueue::SentMessage() {
+void PacketProtocol::OutstandingMessages::SentMessage(
+ Transaction* transaction) {
ScopedModule<PacketProtocol> in_pp(protocol_);
- OVERNET_TRACE(DEBUG) << "SendQueue.SentMessage: requests=" << requests_.size()
- << " scheduled_tail_loss_probe="
- << scheduled_tail_loss_probe_.Map(
- [](const ScheduledTailLossProbe& tlp) {
- return tlp.when;
- });
- assert(scheduled_);
- scheduled_ = false;
- protocol_->InTransaction([this](Transaction* t) {
- if (!requests_.empty()) {
- Schedule();
- return;
- }
- if (!last_send_was_tail_loss_probe_ && !t->Closing()) {
- ScheduleTailLossProbe();
- return;
- }
- protocol_->state_->send_queue.Reset();
- });
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.SentMessage: "
+ << OutstandingString();
+ if (unsent_tip_ - send_tip_ != outstanding_.size()) {
+ transaction->StartSendingOnCompletion();
+ }
+ if (protocol_->probe_tails_ && !last_send_was_tail_probe_) {
+ ScheduleAck();
+ }
}
-void PacketProtocol::SendQueue::ScheduleTailLossProbe() {
+void PacketProtocol::OutstandingMessages::ScheduleAck() {
+ ScopedModule<PacketProtocol> in_pp(protocol_);
const auto when = protocol_->timer_->Now() + protocol_->TailLossProbeDelay();
- OVERNET_TRACE(DEBUG) << "SendQueue.ScheduleTailLossProbe: requests="
- << requests_.size() << " scheduled_tail_loss_probe="
- << scheduled_tail_loss_probe_.Map(
- [](const ScheduledTailLossProbe& tlp) {
- return tlp.when;
- })
- << " when=" << when;
- if (!requests_.empty()) {
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.ScheduleTailLossProbe";
+ if (unsent_tip_ - send_tip_ != outstanding_.size()) {
+ protocol_->stats_
+ .tail_loss_probes_cancelled_because_requests_already_queued++;
return;
}
- if (scheduled_tail_loss_probe_.has_value() &&
- scheduled_tail_loss_probe_->when <= when) {
+ if (tail_probe_timeout_.has_value() && tail_probe_timeout_->when <= when) {
+ protocol_->stats_
+ .tail_loss_probes_cancelled_because_probe_already_scheduled++;
return;
}
- scheduled_tail_loss_probe_.Reset(
+ tail_probe_timeout_.Reset(
protocol_->timer_, when, [this](const Status& status) {
ScopedModule<PacketProtocol> in_pp(protocol_);
- OVERNET_TRACE(DEBUG) << "SendQueue.ScheduleTailLossProbe --> " << status
- << " requests=" << requests_.size();
+ OVERNET_TRACE(DEBUG)
+ << "OutstandingMessages.ScheduleTailLossProbe --> " << status;
if (status.is_error()) {
+ protocol_->stats_.tail_loss_probes_cancelled_after_timer_created++;
return;
}
- scheduled_tail_loss_probe_.Reset();
- ForceSendAck();
+ tail_probe_timeout_.Reset();
+ protocol_->stats_.tail_loss_probes_scheduled++;
+ protocol_->InTransaction(
+ [this](Transaction* transaction) { ForceSendAck(transaction); });
});
}
-void PacketProtocol::SendQueue::ForceSendAck() {
- if (!scheduled_) {
- protocol_->InTransaction([](Transaction* t) { t->ScheduleForcedAck(); });
- }
+void PacketProtocol::OutstandingMessages::ForceSendAck(
+ Transaction* transaction) {
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.ForceSendAck";
+ Schedule(transaction, OutstandingPacket::PendingTailProbe{});
}
///////////////////////////////////////////////////////////////////////////////
@@ -394,18 +437,23 @@
void PacketProtocol::Process(TimeStamp received, SeqNum seq_num, Slice slice,
ProcessCallback handle_message) {
+ assert(refs_);
ScopedModule<PacketProtocol> scoped_module(this);
Transaction transaction(this);
OVERNET_TRACE(DEBUG) << "Process: " << slice;
+ stats_.incoming_packet_count++;
+
if (!state_.has_value()) {
return;
}
+ state_->bdp_estimator.ReceivedBytes(slice.length());
state_->ack_sender.NeedAck(
+ &transaction,
state_->received_queue.Received(seq_num, received, [&](uint64_t seq_idx) {
- return ProcessMessage(seq_idx, std::move(slice), received,
+ return ProcessMessage(&transaction, seq_idx, std::move(slice), received,
std::move(handle_message));
}));
state_->outstanding_messages.ReceivedPacket();
@@ -420,6 +468,7 @@
OVERNET_TRACE(DEBUG) << "Process seq:" << seq_idx;
if (seq_idx < received_tip_) {
+ stats_->ack_not_required_historic_sequence++;
return AckUrgency::NOT_REQUIRED;
}
@@ -430,6 +479,7 @@
auto* received_packet = &received_packets_[seq_idx - received_tip_];
if (received_packet->state != ReceiveState::UNKNOWN) {
OVERNET_TRACE(DEBUG) << "frozen as " << received_packet->state;
+ stats_->ack_not_required_frozen_sequence++;
return AckUrgency::NOT_REQUIRED;
}
@@ -449,43 +499,52 @@
switch (pmr) {
case ProcessMessageResult::NOT_PROCESSED:
// Failed processing packets shouldn't generate traffic.
+ stats_->ack_not_required_invalid_packet++;
return AckUrgency::NOT_REQUIRED;
case ProcessMessageResult::NACK:
optional_ack_run_length_ = 0;
*received_packet = ReceivedPacket{ReceiveState::NOT_RECEIVED, received};
+ stats_->ack_required_immediately_due_to_nack++;
// Always send a nack as soon as we realize one is necessary.
return AckUrgency::SEND_IMMEDIATELY;
case ProcessMessageResult::OPTIONAL_ACK:
// If we get a single ack without a payload, we suppress sending a reply
// ack.
optional_ack_run_length_++;
+ *received_packet =
+ ReceivedPacket{ReceiveState::RECEIVED_PURE_ACK, received};
if (optional_ack_run_length_ < 5) {
- *received_packet = ReceivedPacket{ReceiveState::RECEIVED, received};
+ stats_->ack_not_required_short_optional_run++;
return AckUrgency::NOT_REQUIRED;
}
- [[fallthrough]];
+ stats_->ack_required_soon_ack_received++;
+ optional_ack_run_length_ = 0;
+ return AckUrgency::SEND_SOON;
case ProcessMessageResult::ACK: {
optional_ack_run_length_ = 0;
*received_packet = ReceivedPacket{ReceiveState::RECEIVED, received};
- int num_received = 0;
- for (const auto& pkt : received_packets_) {
- switch (pkt.state) {
- case ReceiveState::RECEIVED:
- num_received++;
- if (num_received >= 3) {
- return AckUrgency::SEND_IMMEDIATELY;
- }
- break;
- default:
- break;
+ static const uint64_t kMaxIncomingBeforeForcedAck = 3;
+ if (seq_idx - received_tip_ >= kMaxIncomingBeforeForcedAck) {
+ for (auto idx = seq_idx - received_tip_ - kMaxIncomingBeforeForcedAck;
+ idx < seq_idx - received_tip_; idx++) {
+ if (received_packets_[idx].state != ReceiveState::RECEIVED) {
+ goto dont_send_immediately;
+ }
}
+ *received_packet = ReceivedPacket{
+ ReceiveState::RECEIVED_AND_ACKED_IMMEDIATELY, received};
+ stats_->ack_required_immediately_due_to_multiple_receives++;
+ return AckUrgency::SEND_IMMEDIATELY;
}
+ dont_send_immediately:
// Got some data, make sure there's an ack scheduled soon.
+ stats_->ack_required_soon_data_received++;
return AckUrgency::SEND_SOON;
}
case ProcessMessageResult::ACK_URGENTLY: {
optional_ack_run_length_ = 0;
*received_packet = ReceivedPacket{ReceiveState::RECEIVED, received};
+ stats_->ack_required_immediately_due_to_partial_ack++;
return AckUrgency::SEND_IMMEDIATELY;
}
}
@@ -507,7 +566,7 @@
}
PacketProtocol::ProcessMessageResult PacketProtocol::ProcessMessage(
- uint64_t seq_idx, Slice slice, TimeStamp received,
+ Transaction* transaction, uint64_t seq_idx, Slice slice, TimeStamp received,
ProcessCallback handle_message) {
using StatusType = StatusOr<IncomingMessage*>;
@@ -585,7 +644,8 @@
handle_message(nullptr);
}
if (ack.has_value()) {
- state_->outstanding_messages.ProcessValidAck(ack.Take(), received);
+ state_->outstanding_messages.ProcessValidAck(transaction, ack.Take(),
+ received);
}
return ack_result;
}
@@ -607,22 +667,23 @@
continue;
}
const OutstandingPacket& pkt = outstanding_[nack_seq - send_tip_];
- if (pkt.state == OutstandingPacketState::ACKED) {
+ if (std::holds_alternative<OutstandingPacket::Acked>(pkt.state)) {
// Previously acked packet becomes nacked: this is an error.
return Status(StatusCode::INVALID_ARGUMENT,
"Previously acked packet becomes nacked");
}
}
for (size_t i = 0; i < ack.ack_to_seq() - send_tip_; i++) {
- if (!outstanding_[i].bbr_sent_packet.has_value()) {
+ if (std::holds_alternative<OutstandingPacket::Pending>(
+ outstanding_[i].state)) {
return Status(StatusCode::INVALID_ARGUMENT, "Ack/nack unsent sequence");
}
}
return Status::Ok();
}
-void PacketProtocol::OutstandingMessages::ProcessValidAck(AckFrame ack,
- TimeStamp received) {
+void PacketProtocol::OutstandingMessages::ProcessValidAck(
+ Transaction* transaction, AckFrame ack, TimeStamp received) {
// Basic validation. Can assert anything that should be an error because
// ValidateAck should have been called prior.
if (ack.ack_to_seq() < send_tip_) {
@@ -638,98 +699,88 @@
received);
}
- AckProcessor ack_processor(this,
- TimeDelta::FromMicroseconds(ack.ack_delay_us()));
+ const auto queue_delay = TimeDelta::FromMicroseconds(ack.ack_delay_us());
// Fail any nacked packets.
// Iteration is from oldest packet to newest, such that the OLDEST nacked
// message is the most likely to be sent first. This has the important
// consequence that if the packet was a fragment of a large message that was
- // rejected due to buffering, the earlier pieces (that are more likely to fit)
- // are retransmitted first.
+ // rejected due to buffering, the earlier pieces (that are more likely to
+ // fit) are retransmitted first.
for (auto nack_seq : ack.nack_seqs()) {
- ack_processor.Nack(nack_seq, Status::Unavailable());
+ Nack(transaction, nack_seq, queue_delay, Status::Unavailable());
}
// Clear out outstanding packet references, propagating acks.
for (size_t i = send_tip_; i <= ack.ack_to_seq(); i++) {
OutstandingPacket& pkt = outstanding_[i - send_tip_];
- if (!pkt.request.empty()) {
- ack_processor.Ack(i);
+ if (std::holds_alternative<OutstandingPacket::Sent>(pkt.state)) {
+ Ack(transaction, i, queue_delay);
}
}
}
-void PacketProtocol::OutstandingMessages::AckProcessor::Ack(uint64_t ack_seq) {
- OutstandingPacket& pkt =
- outstanding_->outstanding_[ack_seq - outstanding_->send_tip_];
- auto request = std::move(pkt.request);
- if (!request.empty()) {
- assert(pkt.state == OutstandingPacketState::SENT);
- pkt.state = OutstandingPacketState::ACKED;
- bbr_ack_.acked_packets.push_back(*pkt.bbr_sent_packet);
- request.Ack(Status::Ok());
- }
+void PacketProtocol::OutstandingMessages::Ack(Transaction* transaction,
+ uint64_t ack_seq,
+ TimeDelta queue_delay) {
+ OutstandingPacket& pkt = outstanding_[ack_seq - send_tip_];
+ auto& sent_packet = std::get<OutstandingPacket::Sent>(pkt.state);
+ auto request = std::move(sent_packet.request);
+ auto& bbr_sent_packet = sent_packet.bbr_sent_packet;
+ bbr_sent_packet.send_time += queue_delay;
+ transaction->QueueAck(bbr_sent_packet);
+ protocol_->state_->bdp_estimator.AckPacket(sent_packet.bdp_packet);
+ pkt.state = OutstandingPacket::Acked{};
+ transaction->IncrementOutstandingTipOnCompletion();
+ request.Ack(Status::Ok());
}
-void PacketProtocol::OutstandingMessages::AckProcessor::Nack(
- uint64_t nack_seq, const Status& status) {
+void PacketProtocol::OutstandingMessages::Nack(Transaction* transaction,
+ uint64_t nack_seq,
+ TimeDelta queue_delay,
+ const Status& status) {
OVERNET_TRACE(DEBUG) << "AckProcessor.Nack: seq=" << nack_seq
- << " status=" << status
- << " send_tip=" << outstanding_->send_tip_;
+ << " status=" << status << " send_tip=" << send_tip_;
assert(status.is_error());
- if (outstanding_->protocol_->state_.has_value()) {
- outstanding_->protocol_->state_->ack_sender.OnNack(nack_seq);
+ if (protocol_->state_.has_value()) {
+ protocol_->state_->ack_sender.OnNack(
+ transaction, nack_seq, status.code() == StatusCode::CANCELLED);
}
- if (nack_seq < outstanding_->send_tip_) {
+ if (nack_seq < send_tip_) {
return;
}
- OutstandingPacket& pkt =
- outstanding_->outstanding_[nack_seq - outstanding_->send_tip_];
- auto request = std::move(pkt.request);
+ OutstandingPacket& pkt = outstanding_[nack_seq - send_tip_];
OVERNET_TRACE(DEBUG) << "AckProcessor.Nack: seq=" << nack_seq
- << " has_request=" << !request.empty()
<< " state=" << pkt.state;
- if (request.empty()) {
- return;
+ if (std::holds_alternative<OutstandingPacket::Pending>(pkt.state) ||
+ std::holds_alternative<OutstandingPacket::PendingTailProbe>(pkt.state)) {
+ pkt.state = OutstandingPacket::Nacked{};
+ transaction->IncrementOutstandingTipOnCompletion();
+ } else if (auto* sent_packet =
+ std::get_if<OutstandingPacket::Sent>(&pkt.state)) {
+ assert(sent_packet->bbr_sent_packet.outgoing.sequence == nack_seq);
+ auto& bbr_sent_packet = sent_packet->bbr_sent_packet;
+ bbr_sent_packet.send_time += queue_delay;
+ transaction->QueueNack(bbr_sent_packet);
+ auto request = std::move(sent_packet->request);
+ pkt.state = OutstandingPacket::Nacked{};
+ transaction->IncrementOutstandingTipOnCompletion();
+ request.Ack(status);
+ } else if (std::holds_alternative<OutstandingPacket::Nacked>(pkt.state)) {
+ } else {
+ // Previously acked packet becomes nacked: this is an error that should be
+ // diagnosed during validation.
+ abort();
}
- switch (pkt.state) {
- case OutstandingPacketState::PENDING:
- pkt.state = OutstandingPacketState::NACKED;
- break;
- case OutstandingPacketState::SENT:
- assert(pkt.bbr_sent_packet.has_value());
- assert(pkt.bbr_sent_packet->outgoing.sequence == nack_seq);
- bbr_ack_.nacked_packets.push_back(*pkt.bbr_sent_packet);
- pkt.state = OutstandingPacketState::NACKED;
- break;
- case OutstandingPacketState::NACKED:
- break;
- default:
- // Previously acked packet becomes nacked: this is an error.
- abort();
- }
- request.Ack(status);
}
-PacketProtocol::OutstandingMessages::AckProcessor::~AckProcessor() {
- bool empty = true;
- // Offset send_time to account for queuing delay on peer.
- for (auto& pkt : bbr_ack_.acked_packets) {
- empty = false;
- pkt.send_time = pkt.send_time + queue_delay_;
+void PacketProtocol::OutstandingMessages::IncrementTip() {
+ while (!outstanding_.empty() && outstanding_.front().is_finalized()) {
+ send_tip_++;
+ outstanding_.pop_front();
}
- for (auto& pkt : bbr_ack_.nacked_packets) {
- empty = false;
- pkt.send_time = pkt.send_time + queue_delay_;
- }
- if (!empty && outstanding_->protocol_->state_.has_value()) {
- outstanding_->protocol_->state_->bbr_.OnAck(bbr_ack_);
- }
- while (!outstanding_->outstanding_.empty() &&
- outstanding_->outstanding_.front().request.empty()) {
- outstanding_->send_tip_++;
- outstanding_->outstanding_.pop_front();
+ if (unsent_tip_ < send_tip_) {
+ unsent_tip_ = send_tip_;
}
}
@@ -765,40 +816,57 @@
return "{" + out.str() + "}";
}
-void PacketProtocol::AckSender::NeedAck(AckUrgency urgency) {
+void PacketProtocol::AckSender::NeedAck(Transaction* transaction,
+ AckUrgency urgency) {
+ OVERNET_TRACE(DEBUG) << "AckSender.NeedAck"
+ << " urgency=" << urgency << " (from " << urgency_ << ")"
+ << " all_acks_acknowledged=" << all_acks_acknowledged_
+ << " sent_full_acks=" << SentFullAcksString();
+
if (urgency <= urgency_) {
return;
}
- OVERNET_TRACE(DEBUG) << "AckSender.NeedAck"
- << " urgency=" << urgency
- << " all_acks_acknowledged=" << all_acks_acknowledged_
- << " sent_full_acks=" << SentFullAcksString();
urgency_ = urgency;
+ assert(protocol_->state_.has_value());
sent_full_acks_.clear();
all_acks_acknowledged_ = false;
- assert(protocol_->state_.has_value());
- if (!protocol_->state_->send_queue.has_value()) {
- protocol_->state_->send_queue.Reset(protocol_);
- }
switch (urgency_) {
case AckUrgency::NOT_REQUIRED:
abort();
- case AckUrgency::SEND_SOON:
- protocol_->state_->send_queue->ScheduleTailLossProbe();
- break;
+ case AckUrgency::SEND_SOON: {
+ const auto when =
+ protocol_->timer_->Now() + protocol_->TailLossProbeDelay();
+ OVERNET_TRACE(DEBUG) << "AckSender.NeedAck: schedule ack start for "
+ << when;
+ suppress_need_acks_ = true;
+ send_ack_timer_.Reset(protocol_->timer_, when, [this](const Status& status) {
+ OVERNET_TRACE(DEBUG) << "AckSender.NeedAck: ack start --> " << status;
+ if (status.is_error()) {
+ return;
+ }
+ suppress_need_acks_ = false;
+ protocol_->stats_
+ .tail_loss_probe_scheduled_because_ack_required_soon_timer_expired++;
+ protocol_->state_->outstanding_messages.ScheduleAck();
+ });
+ } break;
case AckUrgency::SEND_IMMEDIATELY:
- protocol_->state_->send_queue->ForceSendAck();
+ suppress_need_acks_ = false;
+ send_ack_timer_.Reset();
+ protocol_->state_->outstanding_messages.ForceSendAck(transaction);
break;
}
}
-void PacketProtocol::AckSender::AckSent(uint64_t seq_idx, bool partial) {
+void PacketProtocol::AckSender::AckSent(Transaction* transaction,
+ uint64_t seq_idx, bool partial) {
OVERNET_TRACE(DEBUG) << "AckSender.AckSent seq_idx=" << seq_idx
<< " partial=" << partial
<< " all_acks_acknowledged=" << all_acks_acknowledged_
<< " sent_full_acks=" << SentFullAcksString();
+ send_ack_timer_.Reset();
if (!sent_full_acks_.empty()) {
assert(seq_idx > sent_full_acks_.back());
}
@@ -806,11 +874,13 @@
if (!partial) {
sent_full_acks_.push_back(seq_idx);
} else if (sent_full_acks_.empty()) {
- NeedAck(AckUrgency::SEND_SOON);
+ protocol_->stats_.ack_required_soon_continue_partial_after_ack++;
+ NeedAck(transaction, AckUrgency::SEND_SOON);
}
}
-void PacketProtocol::AckSender::OnNack(uint64_t seq) {
+void PacketProtocol::AckSender::OnNack(Transaction* transaction, uint64_t seq,
+ bool shutting_down) {
OVERNET_TRACE(DEBUG) << "AckSender.OnNack"
<< " sent_full_acks=" << SentFullAcksString()
<< " seq=" << seq
@@ -821,8 +891,9 @@
return;
}
sent_full_acks_.erase(it);
- if (sent_full_acks_.empty()) {
- NeedAck(AckUrgency::SEND_SOON);
+ if (sent_full_acks_.empty() && !shutting_down) {
+ protocol_->stats_.ack_required_soon_all_acks_nacked++;
+ NeedAck(transaction, AckUrgency::SEND_SOON);
}
}
@@ -845,13 +916,13 @@
void PacketProtocol::OutstandingMessages::ScheduleRetransmit() {
OVERNET_TRACE(DEBUG) << "OutstandingMessages.ScheduleRetransmit: rto_timer="
- << rto_timer_.has_value()
+ << retransmit_timeout_.has_value()
<< " deadline=" << RetransmitDeadline();
- if (rto_timer_.has_value()) {
+ if (retransmit_timeout_.has_value()) {
return;
}
if (auto timeout = RetransmitDeadline(); timeout.has_value()) {
- rto_timer_.Reset(
+ retransmit_timeout_.Reset(
protocol_->timer_, *timeout,
[protocol = protocol_](const Status& status) {
ScopedModule in_pp(protocol);
@@ -863,68 +934,70 @@
return;
}
if (status.is_error()) {
- protocol->state_->outstanding_messages.NackAll();
+ protocol->state_->outstanding_messages.NackAll(transaction);
return;
}
- protocol->state_->outstanding_messages.CheckRetransmit();
+ protocol->state_->outstanding_messages.CheckRetransmit(transaction);
});
});
}
}
Optional<TimeStamp> PacketProtocol::OutstandingMessages::RetransmitDeadline() {
+ OVERNET_TRACE(DEBUG) << "OutstandingMessages.RetransmitDeadline: "
+ << OutstandingString();
for (const auto& outstanding : outstanding_) {
- if (outstanding.bbr_sent_packet.has_value() &&
- outstanding.state == OutstandingPacketState::SENT) {
- return outstanding.bbr_sent_packet->send_time +
- protocol_->RetransmitDelay();
+ if (!outstanding.is_finalized()) {
+ return outstanding.sent + protocol_->RetransmitDelay();
}
}
return Nothing;
}
-void PacketProtocol::OutstandingMessages::CheckRetransmit() {
+void PacketProtocol::OutstandingMessages::CheckRetransmit(
+ Transaction* transaction) {
if (!protocol_->state_.has_value()) {
return;
}
- rto_timer_.Reset();
+ retransmit_timeout_.Reset();
const auto nack_before =
protocol_->timer_->Now() - protocol_->RetransmitDelay();
OVERNET_TRACE(DEBUG) << "OutstandingMessages.CheckRetransmit: nack_before="
<< nack_before
<< " (current_rtt=" << protocol_->CurrentRTT() << ")";
- AckProcessor ack_processor(this, TimeDelta::Zero());
for (size_t i = 0; i < outstanding_.size(); i++) {
- if (!outstanding_[i].bbr_sent_packet.has_value()) {
+ if (outstanding_[i].is_finalized()) {
OVERNET_TRACE(DEBUG) << "OutstandingMessages.CheckRetransmit: seq "
- << (send_tip_ + i) << " not sent: STOP";
+ << (send_tip_ + i) << " finalized: STOP";
break;
}
- if (outstanding_[i].bbr_sent_packet->send_time > nack_before) {
+ const auto sent = outstanding_[i].sent;
+ if (sent > nack_before) {
OVERNET_TRACE(DEBUG) << "OutstandingMessages.CheckRetransmit: seq "
- << (send_tip_ + i) << " sent at "
- << outstanding_[i].bbr_sent_packet->send_time
+ << (send_tip_ + i) << " sent at " << sent
<< ": STOP";
break;
}
OVERNET_TRACE(DEBUG) << "OutstandingMessages.CheckRetransmit: seq "
- << (send_tip_ + i) << " sent at "
- << outstanding_[i].bbr_sent_packet->send_time
- << ": NACK";
- ack_processor.Nack(send_tip_ + i, Status::Unavailable());
+ << (send_tip_ + i) << " sent at " << sent << ": NACK";
+ Nack(transaction, send_tip_ + i, TimeDelta::Zero(), Status::Unavailable());
}
ScheduleRetransmit();
}
-void PacketProtocol::OutstandingMessages::NackAll() {
+PacketProtocol::OutstandingMessages::~OutstandingMessages() {
+ protocol_->InTransaction(
+ [this](Transaction* transaction) { NackAll(transaction); });
+}
+
+void PacketProtocol::OutstandingMessages::NackAll(Transaction* transaction) {
OVERNET_TRACE(DEBUG) << "OutstandingMessages.NackAll";
- AckProcessor ack_processor(this, TimeDelta::Zero());
for (uint64_t i = send_tip_, end = send_tip_ + outstanding_.size(); i < end;
i++) {
- if (outstanding_[i - send_tip_].request.empty()) {
+ if (outstanding_[i - send_tip_].is_finalized()) {
continue;
}
- ack_processor.Nack(i, Status::Cancelled());
+ Nack(transaction, i, TimeDelta::Zero(), Status::Cancelled());
}
}
@@ -932,7 +1005,7 @@
// Utilities
TimeDelta PacketProtocol::CurrentRTT() const {
- return std::max(TimeDelta::FromMilliseconds(1), state_->bbr_.rtt());
+ return std::max(TimeDelta::FromMilliseconds(1), state_->bbr.rtt());
}
TimeDelta PacketProtocol::RetransmitDelay() const {
diff --git a/src/connectivity/overnet/lib/packet_protocol/packet_protocol.h b/src/connectivity/overnet/lib/packet_protocol/packet_protocol.h
index 23c859d..58d0e3a 100644
--- a/src/connectivity/overnet/lib/packet_protocol/packet_protocol.h
+++ b/src/connectivity/overnet/lib/packet_protocol/packet_protocol.h
@@ -7,13 +7,16 @@
#include <deque>
#include <map>
#include <queue>
+#include <variant>
#include "src/connectivity/overnet/lib/environment/timer.h"
#include "src/connectivity/overnet/lib/environment/trace.h"
#include "src/connectivity/overnet/lib/labels/seq_num.h"
#include "src/connectivity/overnet/lib/packet_protocol/bbr.h"
+#include "src/connectivity/overnet/lib/packet_protocol/bdp_estimator.h"
#include "src/connectivity/overnet/lib/protocol/ack_frame.h"
#include "src/connectivity/overnet/lib/protocol/varint.h"
+#include "src/connectivity/overnet/lib/stats/link.h"
#include "src/connectivity/overnet/lib/vocabulary/callback.h"
#include "src/connectivity/overnet/lib/vocabulary/lazy_slice.h"
#include "src/connectivity/overnet/lib/vocabulary/once_fn.h"
@@ -121,7 +124,11 @@
// either explicitly dropped or destroyed.
class ProtocolRef {
public:
- ProtocolRef(PacketProtocol* protocol) : protocol_(protocol) {
+ ProtocolRef(PacketProtocol* protocol, bool primary_ref = false)
+ : protocol_(protocol) {
+ if (!primary_ref) {
+ assert(protocol_->refs_ != 0);
+ }
protocol_->refs_++;
}
~ProtocolRef() {
@@ -190,118 +197,150 @@
class PacketSend;
+ // A Transaction is created to describe a set of changes to a PacketProtocol.
+ // One is created in response to every Process() call, and in response to
+ // sends that are outside of incoming messages.
+ // Only one Transaction can be active at a time.
+ // A Transaction can process only one incoming message.
+ // A Transaction may process any number (including zero) sends.
+ class Transaction {
+ public:
+ Transaction(PacketProtocol* protocol);
+ ~Transaction();
+
+ void QuiesceOnCompletion(Callback<void> callback);
+ bool Closing() const { return quiesce_ || !protocol_->state_.has_value(); }
+
+ void StartSendingOnCompletion() { start_sending_ = true; }
+ void IncrementOutstandingTipOnCompletion() {
+ increment_outstanding_tip_ = true;
+ }
+
+ void QueueAck(BBR::SentPacket packet) {
+ bbr_ack_.Force()->acked_packets.push_back(packet);
+ }
+ void QueueNack(BBR::SentPacket packet) {
+ bbr_ack_.Force()->nacked_packets.push_back(packet);
+ }
+
+ private:
+ PacketProtocol* const protocol_;
+ bool quiesce_ = false;
+ bool start_sending_ = false;
+ bool increment_outstanding_tip_ = false;
+ Optional<BBR::Ack> bbr_ack_;
+ };
+
// OutstandingMessages tracks messages that are sent but not yet acknowledged.
class OutstandingMessages {
public:
OutstandingMessages(PacketProtocol* protocol);
- ~OutstandingMessages() { NackAll(); }
- void Send(BBR::TransmitRequest bbr_request, SendRequestHdl request);
+ ~OutstandingMessages();
Status ValidateAck(const AckFrame& ack) const;
- void ProcessValidAck(AckFrame ack, TimeStamp received);
+ void ProcessValidAck(Transaction* transaction, AckFrame ack,
+ TimeStamp received);
void ReceivedPacket() { ScheduleRetransmit(); }
+ void Schedule(Transaction* transaction, SendRequestHdl message);
+ void StartSending();
+
+ void ScheduleAck();
+ void ForceSendAck(Transaction* t);
+
+ void IncrementTip();
private:
friend class PacketSend;
- Slice GeneratePacket(BBR::TransmitRequest bbr_request, uint64_t seq_idx,
- LazySliceArgs args);
- void CancelPacket(uint64_t seq_idx);
+ Slice GeneratePacket(Transaction* transaction,
+ BBR::TransmitRequest bbr_request, LazySliceArgs args);
+ void SentMessage(Transaction* transaction);
+ void CancelledMessage(Transaction* transaction);
+
void FinishedSending();
void ScheduleRetransmit();
Optional<TimeStamp> RetransmitDeadline();
- void CheckRetransmit();
- void NackAll();
+ void CheckRetransmit(Transaction* transaction);
+ void NackAll(Transaction* transaction);
+ void Send(BBR::TransmitRequest bbr_request, SendRequestHdl request);
- enum class OutstandingPacketState : uint8_t {
- PENDING,
- SENT,
- ACKED,
- NACKED,
- };
-
- friend std::ostream& operator<<(std::ostream& out,
- OutstandingPacketState state) {
- switch (state) {
- case OutstandingPacketState::PENDING:
- return out << "PENDING";
- case OutstandingPacketState::SENT:
- return out << "SENT";
- case OutstandingPacketState::ACKED:
- return out << "ACKED";
- case OutstandingPacketState::NACKED:
- return out << "NACKED";
- }
- }
+ void Ack(Transaction* transaction, uint64_t seq, TimeDelta queue_delay);
+ void Nack(Transaction* transaction, uint64_t seq, TimeDelta queue_delay,
+ const Status& status);
struct OutstandingPacket {
- OutstandingPacketState state;
+ struct PendingTailProbe {};
+
+ struct Pending {
+ SendRequestHdl request;
+ };
+
+ struct Sent {
+ SendRequestHdl request;
+ BBR::SentPacket bbr_sent_packet;
+ BdpEstimator::PerPacketData bdp_packet;
+ };
+
+ struct Acked {};
+ struct Nacked {};
+
+ using State =
+ std::variant<PendingTailProbe, Pending, Sent, Acked, Nacked>;
+
+ TimeStamp sent;
uint64_t max_seen_sequence_at_send;
- Optional<BBR::SentPacket> bbr_sent_packet;
- SendRequestHdl request;
+ State state;
+
+ bool has_request() const {
+ return std::holds_alternative<Pending>(state) ||
+ std::holds_alternative<Sent>(state);
+ }
+
+ bool is_finalized() const {
+ return std::holds_alternative<Acked>(state) ||
+ std::holds_alternative<Nacked>(state);
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const State& state) {
+ if (std::holds_alternative<PendingTailProbe>(state)) {
+ return out << "PENDING_TAIL_PROBE";
+ } else if (std::holds_alternative<Pending>(state)) {
+ return out << "PENDING";
+ } else if (std::holds_alternative<Sent>(state)) {
+ return out << "SENT";
+ } else if (std::holds_alternative<Acked>(state)) {
+ return out << "ACKED";
+ } else if (std::holds_alternative<Nacked>(state)) {
+ return out << "NACKED";
+ } else {
+ abort();
+ }
+ }
};
- // Assists processing a set of acks/nacks
- class AckProcessor {
- public:
- AckProcessor(OutstandingMessages* outstanding, TimeDelta queue_delay)
- : outstanding_(outstanding), queue_delay_(queue_delay) {}
- ~AckProcessor();
-
- void Ack(uint64_t seq);
- void Nack(uint64_t seq, const Status& status);
-
- private:
- OutstandingMessages* const outstanding_;
- const TimeDelta queue_delay_;
- BBR::Ack bbr_ack_;
- };
+ void Schedule(Transaction* transaction,
+ OutstandingPacket::State outstanding_packet_state);
PacketProtocol* const protocol_;
uint64_t send_tip_ = 1;
+ uint64_t unsent_tip_ = 1;
uint64_t max_outstanding_size_ = 1;
uint64_t last_sent_ack_ = 0;
+ bool last_send_was_tail_probe_ = false;
std::deque<OutstandingPacket> outstanding_;
- Optional<Timeout> rto_timer_;
- };
+ std::string OutstandingString() const;
- // SendQueue groups together pending sends in the protocol and manages writing
- // them out.
- class SendQueue {
- public:
- SendQueue(PacketProtocol* protocol) : protocol_(protocol) {}
-
- // Schedule this queue to be sent. Should only be used as directed.
- void Schedule();
-
- // A message was just sent: possibly schedule the next one.
- void SentMessage();
-
- // Ensure a tail loss probe is scheduled: used to force acks to be sent on
- // idle connections.
- void ScheduleTailLossProbe();
-
- // Force a packet to be sent with an ack.
- void ForceSendAck();
-
- // Returns true if send request addition triggers the need for
- // Schedule to be called.
- [[nodiscard]] bool Add(SendRequestHdl hdl);
-
- private:
- PacketProtocol* const protocol_;
- bool scheduled_ = false;
- bool last_send_was_tail_loss_probe_ = false;
- Optional<BBR::TransmitRequest> transmit_request_;
- std::queue<SendRequestHdl> requests_;
- struct ScheduledTailLossProbe {
+ struct TailProbeTimeout {
template <class F>
- ScheduledTailLossProbe(Timer* timer, TimeStamp when, F f)
+ TailProbeTimeout(Timer* timer, TimeStamp when, F f)
: when(when), timeout(timer, when, std::move(f)) {}
TimeStamp when;
Timeout timeout;
};
- Optional<ScheduledTailLossProbe> scheduled_tail_loss_probe_;
+
+ Optional<Timeout> retransmit_timeout_;
+ Optional<TailProbeTimeout> tail_probe_timeout_;
+ Optional<BBR::TransmitRequest> transmit_request_;
};
enum class AckUrgency { NOT_REQUIRED, SEND_SOON, SEND_IMMEDIATELY };
@@ -321,19 +360,22 @@
public:
AckSender(PacketProtocol* protocol);
- void NeedAck(AckUrgency urgency);
+ void NeedAck(Transaction* transaction, AckUrgency urgency);
bool ShouldSendAck() const {
- return !all_acks_acknowledged_ && sent_full_acks_.empty();
+ return !suppress_need_acks_ && !all_acks_acknowledged_ &&
+ sent_full_acks_.empty();
}
- void AckSent(uint64_t seq_idx, bool partial);
- void OnNack(uint64_t seq_idx);
+ void AckSent(Transaction* transaction, uint64_t seq_idx, bool partial);
+ void OnNack(Transaction* transaction, uint64_t seq_idx, bool shutting_down);
void OnAck(uint64_t seq_idx);
private:
PacketProtocol* const protocol_;
std::vector<uint64_t> sent_full_acks_;
bool all_acks_acknowledged_ = true;
+ bool suppress_need_acks_ = false;
AckUrgency urgency_ = AckUrgency::NOT_REQUIRED;
+ Optional<Timeout> send_ack_timer_;
std::string SentFullAcksString() const;
};
@@ -342,6 +384,8 @@
// need to acknowledge them.
class ReceivedQueue {
public:
+ explicit ReceivedQueue(LinkStats* stats) : stats_(stats) {}
+
// Return true if an ack should be sent now.
template <class F>
[[nodiscard]] AckUrgency Received(SeqNum seq_num, TimeStamp received,
@@ -349,8 +393,8 @@
uint64_t max_seen_sequence() const;
bool CanBuildAck() const { return max_seen_sequence() > 0; }
- AckFrame BuildAck(uint64_t seq_idx, TimeStamp now, uint32_t max_length,
- AckSender* ack_sender);
+ AckFrame BuildAck(Transaction* transaction, uint64_t seq_idx, TimeStamp now,
+ uint32_t max_length, AckSender* ack_sender);
void SetTip(uint64_t seq_idx, TimeStamp received);
private:
@@ -374,7 +418,9 @@
enum class ReceiveState {
UNKNOWN,
NOT_RECEIVED,
+ RECEIVED_PURE_ACK,
RECEIVED,
+ RECEIVED_AND_ACKED_IMMEDIATELY,
};
friend std::ostream& operator<<(std::ostream& out, ReceiveState state) {
@@ -385,6 +431,10 @@
return out << "NOT_RECEIVED";
case ReceiveState::RECEIVED:
return out << "RECEIVED";
+ case ReceiveState::RECEIVED_PURE_ACK:
+ return out << "RECEIVED_PURE_ACK";
+ case ReceiveState::RECEIVED_AND_ACKED_IMMEDIATELY:
+ return out << "RECEIVED_AND_ACKED_IMMEDIATELY";
}
}
@@ -394,37 +444,12 @@
TimeStamp when;
};
std::deque<ReceivedPacket> received_packets_;
- };
-
- // A Transaction is created to describe a set of changes to a PacketProtocol.
- // One is created in response to every Process() call, and in response to
- // sends that are outside of incoming messages.
- // Only one Transaction can be active at a time.
- // A Transaction can process only one incoming message.
- // A Transaction may process any number (including zero) sends.
- class Transaction {
- public:
- Transaction(PacketProtocol* protocol);
- ~Transaction();
- void Send(SendRequestHdl hdl);
- void ScheduleForcedAck() { schedule_send_queue_ = true; }
-
- void QuiesceOnCompletion(Callback<void> callback);
-
- bool Closing() const { return quiesce_ || !protocol_->state_.has_value(); }
-
- private:
- SendQueue* send_queue();
-
- PacketProtocol* const protocol_;
- bool schedule_send_queue_ = false;
- bool quiesce_ = false;
+ LinkStats* const stats_;
};
class PacketSend final {
public:
- PacketSend(PacketProtocol* protocol, uint64_t seq_idx,
- BBR::TransmitRequest bbr_request);
+ PacketSend(PacketProtocol* protocol, BBR::TransmitRequest bbr_request);
~PacketSend();
PacketSend(const PacketSend&) = delete;
PacketSend(PacketSend&&) = default;
@@ -433,7 +458,6 @@
private:
ProtocolRef protocol_;
- uint64_t seq_idx_;
BBR::TransmitRequest bbr_request_;
};
@@ -444,7 +468,7 @@
using RandFunc = BBR::RandFunc;
PacketProtocol(Timer* timer, RandFunc rand, PacketSender* packet_sender,
- const Codec* codec, uint64_t mss);
+ const Codec* codec, uint64_t mss, bool probe_tails);
// Request that a single message be sent.
void Send(SendRequestHdl send_request);
@@ -479,24 +503,30 @@
uint32_t maximum_send_size() const { return maximum_send_size_; }
TimeDelta round_trip_time() const {
- return state_.has_value() ? state_->bbr_.rtt() : TimeDelta::PositiveInf();
+ return state_.has_value() ? state_->bbr.rtt() : TimeDelta::PositiveInf();
}
Bandwidth bottleneck_bandwidth() const {
- return state_.has_value() ? state_->bbr_.bottleneck_bandwidth()
+ return state_.has_value() ? state_->bbr.bottleneck_bandwidth()
: Bandwidth::Zero();
}
+ uint64_t bdp_estimate() const {
+ return state_.has_value() ? state_->bdp_estimator.estimate() : 0;
+ }
static Codec* NullCodec();
+ const LinkStats* stats() const { return &stats_; }
+
/////////////////////////////////////////////////////////////////////////////
// Internal methods.
private:
TimeDelta CurrentRTT() const;
TimeDelta RetransmitDelay() const;
TimeDelta TailLossProbeDelay() const;
- Slice FormatPacket(uint64_t seq_idx, SendRequest* request,
- LazySliceArgs args);
- ProcessMessageResult ProcessMessage(uint64_t seq_idx, Slice slice,
+ Slice FormatPacket(Transaction* transaction, uint64_t seq_idx,
+ SendRequest* request, LazySliceArgs args);
+ ProcessMessageResult ProcessMessage(Transaction* transaction,
+ uint64_t seq_idx, Slice slice,
TimeStamp received,
ProcessCallback handle_message);
// Run closure f in a transaction (creating one if necessary)
@@ -520,22 +550,25 @@
PacketSender* const packet_sender_;
Transaction* active_transaction_ = nullptr;
Callback<void> quiesce_;
+ const bool probe_tails_;
const uint32_t maximum_send_size_;
uint32_t refs_ = 0;
- ProtocolRef master_ref_{this};
+ ProtocolRef primary_ref_{this, true};
struct OpenState {
OpenState(PacketProtocol* protocol, RandFunc rand)
: ack_sender(protocol),
+ received_queue(&protocol->stats_),
outstanding_messages(protocol),
- bbr_(protocol->timer_, std::move(rand), protocol->maximum_send_size_,
- Nothing) {}
+ bbr(protocol->timer_, std::move(rand), protocol->maximum_send_size_,
+ Nothing) {}
AckSender ack_sender;
- Optional<SendQueue> send_queue;
ReceivedQueue received_queue;
OutstandingMessages outstanding_messages;
- BBR bbr_;
+ BBR bbr;
+ BdpEstimator bdp_estimator;
};
Optional<OpenState> state_;
+ LinkStats stats_;
};
template <class GB, class A>
diff --git a/src/connectivity/overnet/lib/packet_protocol/packet_protocol_fuzzer.h b/src/connectivity/overnet/lib/packet_protocol/packet_protocol_fuzzer.h
index 3de9854..0e70b92 100644
--- a/src/connectivity/overnet/lib/packet_protocol/packet_protocol_fuzzer.h
+++ b/src/connectivity/overnet/lib/packet_protocol/packet_protocol_fuzzer.h
@@ -7,6 +7,7 @@
#include <iostream>
#include <map>
#include <random>
+
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
#include "src/connectivity/overnet/lib/packet_protocol/packet_protocol.h"
#include "src/connectivity/overnet/lib/testing/test_timer.h"
@@ -76,9 +77,11 @@
std::mt19937 rng_{12345};
const PacketProtocol::Codec* const codec_;
ClosedPtr<PacketProtocol> pp1_ = MakeClosedPtr<PacketProtocol>(
- &timer_, [this] { return rng_(); }, &sender1_, codec_, kMaxSegmentSize);
+ &timer_, [this] { return rng_(); }, &sender1_, codec_, kMaxSegmentSize,
+ true);
ClosedPtr<PacketProtocol> pp2_ = MakeClosedPtr<PacketProtocol>(
- &timer_, [this] { return rng_(); }, &sender2_, codec_, kMaxSegmentSize);
+ &timer_, [this] { return rng_(); }, &sender2_, codec_, kMaxSegmentSize,
+ true);
};
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/packet_protocol/packet_protocol_test.cc b/src/connectivity/overnet/lib/packet_protocol/packet_protocol_test.cc
index df93bda..b4cedb18 100644
--- a/src/connectivity/overnet/lib/packet_protocol/packet_protocol_test.cc
+++ b/src/connectivity/overnet/lib/packet_protocol/packet_protocol_test.cc
@@ -3,7 +3,9 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/packet_protocol/packet_protocol.h"
+
#include <memory>
+
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/connectivity/overnet/lib/environment/trace_cout.h"
@@ -101,7 +103,7 @@
StrictMock<MockPacketSender> ps;
std::mt19937 rng{123};
MakeClosedPtr<PacketProtocol>(
- &timer, [&rng] { return rng(); }, &ps, GetParam(), kMSS);
+ &timer, [&rng] { return rng(); }, &ps, GetParam(), kMSS, true);
}
TEST_P(PacketProtocolTest, SendOnePacket) {
@@ -114,7 +116,7 @@
StrictMock<MockPacketSender> ps;
std::mt19937 rng{123};
auto packet_protocol = MakeClosedPtr<PacketProtocol>(
- &timer, [&rng] { return rng(); }, &ps, GetParam(), kMSS);
+ &timer, [&rng] { return rng(); }, &ps, GetParam(), kMSS, true);
// Send some dummy data: we expect to see a packet emitted immediately
Slice got_slice;
diff --git a/src/connectivity/overnet/lib/protocol/BUILD.gn b/src/connectivity/overnet/lib/protocol/BUILD.gn
index a4af4e6..83e8b83 100644
--- a/src/connectivity/overnet/lib/protocol/BUILD.gn
+++ b/src/connectivity/overnet/lib/protocol/BUILD.gn
@@ -19,6 +19,8 @@
deps = [
":ack_frame_test",
":routable_message_test",
+ ":stream_framer_test",
+ ":unreliable_framer_test",
":varint_test",
]
}
@@ -114,6 +116,20 @@
]
}
+# reliable_framer
+source_set("reliable_framer") {
+ sources = [
+ "reliable_framer.cc",
+ "reliable_framer.h",
+ ]
+ public_deps = [
+ ":stream_framer",
+ ]
+ deps = [
+ "//src/connectivity/overnet/lib/environment:trace",
+ ]
+}
+
# routable_message
source_set("routable_message") {
sources = [
@@ -160,6 +176,60 @@
]
}
+# stream_framer
+source_set("stream_framer") {
+ sources = [
+ "stream_framer.h",
+ ]
+ public_deps = [
+ "//src/connectivity/overnet/lib/vocabulary:optional",
+ "//src/connectivity/overnet/lib/vocabulary:slice",
+ "//src/connectivity/overnet/lib/vocabulary:status",
+ "//src/connectivity/overnet/lib/environment:trace",
+ ]
+}
+
+source_set("stream_framer_test") {
+ testonly = true
+ sources = [
+ "stream_framer_test.cc",
+ ]
+ public_deps = [
+ ":reliable_framer",
+ ":unreliable_framer",
+ ]
+ deps = [
+ "//third_party/googletest:gtest",
+ ]
+}
+
+# unreliable_framer
+source_set("unreliable_framer") {
+ sources = [
+ "unreliable_framer.h",
+ "unreliable_framer.cc",
+ ]
+ public_deps = [
+ ":stream_framer",
+ ]
+ deps = [
+ "//third_party/zlib:zlib_static",
+ ]
+}
+
+source_set("unreliable_framer_test") {
+ testonly = true
+ sources = [
+ "unreliable_framer_test.cc",
+ ]
+ public_deps = [
+ ":unreliable_framer",
+ ]
+ deps = [
+ "//third_party/googletest:gtest",
+ ]
+}
+
# varint
source_set("varint") {
sources = [
diff --git a/src/connectivity/overnet/lib/protocol/reliable_framer.cc b/src/connectivity/overnet/lib/protocol/reliable_framer.cc
new file mode 100644
index 0000000..2e3da85
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/reliable_framer.cc
@@ -0,0 +1,70 @@
+// 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 "src/connectivity/overnet/lib/protocol/reliable_framer.h"
+
+#include "src/connectivity/overnet/lib/environment/trace.h"
+
+namespace overnet {
+
+ReliableFramer::ReliableFramer() : StreamFramer(Border::Prefix(2), 65536) {}
+ReliableFramer::~ReliableFramer() = default;
+
+void ReliableFramer::Push(Slice data) {
+ OVERNET_TRACE(DEBUG) << "ReliableFramer.Push: " << data;
+ buffered_input_.Append(std::move(data));
+}
+
+StatusOr<Optional<Slice>> ReliableFramer::Pop() {
+ OVERNET_TRACE(DEBUG) << "ReliableFramer.Pop: q=" << buffered_input_;
+ using Sts = StatusOr<Optional<Slice>>;
+
+ const uint8_t *begin = buffered_input_.begin();
+ const uint8_t *p = begin;
+ const uint8_t *end = buffered_input_.end();
+
+ OVERNET_TRACE(DEBUG) << "ReliableFramer.Pop: have " << (end - p) << " bytes";
+
+ if (end - p < 2) {
+ OVERNET_TRACE(DEBUG)
+ << "ReliableFramer.Pop: insufficient bytes to see header";
+ return Nothing;
+ }
+ uint16_t hdr;
+ memcpy(&hdr, p, 2);
+ p += 2;
+
+ const ssize_t segment_length = ssize_t(hdr) + 1;
+ OVERNET_TRACE(DEBUG) << "ReliableFramer.Pop: hdr=" << hdr
+ << " => segment_length " << segment_length;
+
+ if (end - p < segment_length) {
+ return Sts(Nothing);
+ }
+
+ buffered_input_.TrimBegin(p - begin);
+ return Sts(buffered_input_.TakeUntilOffset(segment_length));
+}
+
+bool ReliableFramer::InputEmpty() const {
+ return buffered_input_.length() == 0;
+}
+
+Optional<Slice> ReliableFramer::SkipNoise() { return Nothing; }
+
+Slice ReliableFramer::Frame(Slice data) {
+ OVERNET_TRACE(DEBUG) << "ReliableFramer.Frame: " << data;
+ auto length = data.length();
+
+ if (length == 0) {
+ return data;
+ }
+
+ return data.WithPrefix(2, [length](uint8_t *p) {
+ uint16_t hdr = length - 1;
+ memcpy(p, &hdr, 2);
+ });
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/reliable_framer.h b/src/connectivity/overnet/lib/protocol/reliable_framer.h
new file mode 100644
index 0000000..e81e16b
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/reliable_framer.h
@@ -0,0 +1,28 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
+
+namespace overnet {
+
+// Framer that transports packets on a reliable stream of bytes
+class ReliableFramer final : public StreamFramer {
+ public:
+ ReliableFramer();
+ ~ReliableFramer();
+
+ void Push(Slice data) override;
+ StatusOr<Optional<Slice>> Pop() override;
+ bool InputEmpty() const override;
+ Optional<Slice> SkipNoise() override;
+
+ Slice Frame(Slice data) override;
+
+ private:
+ Slice buffered_input_;
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/stream_framer.h b/src/connectivity/overnet/lib/protocol/stream_framer.h
new file mode 100644
index 0000000..79842ee
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/stream_framer.h
@@ -0,0 +1,48 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/vocabulary/optional.h"
+#include "src/connectivity/overnet/lib/vocabulary/slice.h"
+#include "src/connectivity/overnet/lib/vocabulary/status.h"
+
+namespace overnet {
+
+// Manages the framing and unframing of packets in a stream
+class StreamFramer {
+ public:
+ StreamFramer(Border desired_border, uint32_t maximum_segment_size)
+ : desired_border(desired_border),
+ maximum_segment_size(maximum_segment_size) {}
+ virtual ~StreamFramer() = default;
+
+ const Border desired_border;
+ const uint32_t maximum_segment_size;
+
+ // Input loop:
+ // incoming_data = read_from_stream();
+ // Push(incoming_data);
+ // while (auto frame = Pop()) {
+ // process_frame(*frame);
+ // }
+ virtual void Push(Slice data) = 0;
+ virtual StatusOr<Optional<Slice>> Pop() = 0;
+ // Returns true if nothing is buffered.
+ virtual bool InputEmpty() const = 0;
+ // Skip content if stuck.
+ // Should be called after some appropriate timeout (some small multiple of the
+ // time required to transmit MaximumSegmentSize()).
+ // Allows the framer to skip noise reliably.
+ // Returns what was skipped (or Nothing if a skip is unavailable).
+ virtual Optional<Slice> SkipNoise() = 0;
+
+ // Output loop:
+ // frame = construct_frame(MaximumSegmentSize());
+ // outgoing_data = Frame(frame);
+ // write(*outgoing_data);
+ virtual Slice Frame(Slice data) = 0;
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/stream_framer_test.cc b/src/connectivity/overnet/lib/protocol/stream_framer_test.cc
new file mode 100644
index 0000000..f6494cc
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/stream_framer_test.cc
@@ -0,0 +1,155 @@
+// 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 <gtest/gtest.h>
+
+#include "src/connectivity/overnet/lib/environment/trace.h"
+#include "src/connectivity/overnet/lib/protocol/reliable_framer.h"
+#include "src/connectivity/overnet/lib/protocol/unreliable_framer.h"
+
+namespace overnet {
+namespace stream_framer_test {
+
+struct TestArg {
+ std::function<std::unique_ptr<StreamFramer>()> make_framer;
+ Slice enframe;
+};
+
+struct StreamFramerTest : public ::testing::TestWithParam<TestArg> {};
+
+TEST_P(StreamFramerTest, DeframesFramed) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ const auto enframe = GetParam().enframe;
+ const auto framed = framer->Frame(enframe);
+ framer->Push(framed);
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed << " framed=" << framed;
+ ASSERT_TRUE(deframed->has_value()) << deframed << " framed=" << framed;
+ EXPECT_EQ(enframe, **deframed) << " framed=" << framed;
+ EXPECT_TRUE(framer->InputEmpty());
+}
+
+TEST_P(StreamFramerTest, DeframesOneByteAtATime) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ const auto enframe = GetParam().enframe;
+ const auto framed = framer->Frame(enframe);
+ for (auto c : framed) {
+ auto early_pop = framer->Pop();
+ EXPECT_TRUE(early_pop.is_ok()) << early_pop << " framed=" << framed;
+ EXPECT_FALSE(early_pop->has_value()) << early_pop << " framed=" << framed;
+
+ framer->Push(Slice::RepeatedChar(1, c));
+ }
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed << " framed=" << framed;
+ ASSERT_TRUE(deframed->has_value()) << deframed << " framed=" << framed;
+ EXPECT_EQ(enframe, **deframed) << " framed=" << framed;
+ EXPECT_TRUE(framer->InputEmpty());
+}
+
+template <class T>
+TestArg Test(Slice enframe) {
+ return TestArg{[] { return std::make_unique<T>(); }, enframe};
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ StreamFramerSuite, StreamFramerTest,
+ ::testing::Values(Test<ReliableFramer>(Slice::FromContainer({1, 2, 3})),
+ Test<UnreliableFramer>(Slice::FromContainer({1, 2, 3})),
+ Test<ReliableFramer>(Slice::RepeatedChar(256, 'a')),
+ Test<UnreliableFramer>(Slice::RepeatedChar(256, 'a')),
+ Test<ReliableFramer>(Slice::RepeatedChar(65536, 'a'))));
+
+struct MultiArg {
+ std::function<std::unique_ptr<StreamFramer>()> make_framer;
+ std::vector<Slice> enframe;
+};
+
+struct StreamFramerMulti : public ::testing::TestWithParam<MultiArg> {};
+
+template <class T>
+MultiArg Multi(std::initializer_list<Slice> enframe) {
+ return MultiArg{[] { return std::make_unique<T>(); }, enframe};
+}
+
+TEST_P(StreamFramerMulti, DeframesFramedOneFrameAtATime) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ for (auto enframe : GetParam().enframe) {
+ const auto framed = framer->Frame(enframe);
+ framer->Push(framed);
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed << " framed=" << framed;
+ ASSERT_TRUE(deframed->has_value()) << deframed << " framed=" << framed;
+ EXPECT_EQ(enframe, **deframed) << " framed=" << framed;
+ EXPECT_TRUE(framer->InputEmpty());
+ }
+}
+
+TEST_P(StreamFramerMulti, DeframesOneByteAtATimeOneFrameAtATime) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ for (auto enframe : GetParam().enframe) {
+ const auto framed = framer->Frame(enframe);
+ for (auto c : framed) {
+ auto early_pop = framer->Pop();
+ EXPECT_TRUE(early_pop.is_ok()) << early_pop << " framed=" << framed;
+ EXPECT_FALSE(early_pop->has_value()) << early_pop << " framed=" << framed;
+
+ framer->Push(Slice::RepeatedChar(1, c));
+ }
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed << " framed=" << framed;
+ ASSERT_TRUE(deframed->has_value()) << deframed << " framed=" << framed;
+ EXPECT_EQ(enframe, **deframed) << " framed=" << framed;
+ EXPECT_TRUE(framer->InputEmpty());
+ }
+}
+
+TEST_P(StreamFramerMulti, DeframesFramedAllFramesAtOnce) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ for (auto enframe : GetParam().enframe) {
+ const auto framed = framer->Frame(enframe);
+ framer->Push(framed);
+ }
+ for (auto enframe : GetParam().enframe) {
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed;
+ ASSERT_TRUE(deframed->has_value()) << deframed;
+ EXPECT_EQ(enframe, **deframed);
+ }
+ EXPECT_TRUE(framer->InputEmpty());
+}
+
+TEST_P(StreamFramerMulti, DeframesOneByteAtATimeAllFramesAtOnce) {
+ ScopedSeverity severity(Severity::ERROR);
+ auto framer = GetParam().make_framer();
+ for (auto enframe : GetParam().enframe) {
+ const auto framed = framer->Frame(enframe);
+ for (auto c : framed) {
+ framer->Push(Slice::RepeatedChar(1, c));
+ }
+ }
+ for (auto enframe : GetParam().enframe) {
+ auto deframed = framer->Pop();
+ ASSERT_TRUE(deframed.is_ok()) << deframed;
+ ASSERT_TRUE(deframed->has_value()) << deframed;
+ EXPECT_EQ(enframe, **deframed);
+ }
+ EXPECT_TRUE(framer->InputEmpty());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ StreamFramerMultiSuite, StreamFramerMulti,
+ ::testing::Values(Multi<ReliableFramer>({Slice::FromContainer({1, 2, 3}),
+ Slice::FromContainer({1, 2, 3})}),
+ Multi<UnreliableFramer>({Slice::FromContainer({1, 2, 3}),
+ Slice::FromContainer({1, 2,
+ 3})})));
+
+} // namespace stream_framer_test
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/unreliable_framer.cc b/src/connectivity/overnet/lib/protocol/unreliable_framer.cc
new file mode 100644
index 0000000..9701eec
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/unreliable_framer.cc
@@ -0,0 +1,121 @@
+// 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 "src/connectivity/overnet/lib/protocol/unreliable_framer.h"
+
+#include "third_party/zlib/zlib.h"
+
+namespace overnet {
+
+UnreliableFramer::UnreliableFramer() : StreamFramer(Border{2, 4}, 256) {}
+
+UnreliableFramer::~UnreliableFramer() = default;
+
+void UnreliableFramer::Push(Slice data) {
+ buffered_input_.Append(std::move(data));
+}
+
+StatusOr<Optional<Slice>> UnreliableFramer::Pop() {
+ using Sts = StatusOr<Optional<Slice>>;
+
+ const uint8_t *const begin = buffered_input_.begin();
+ const uint8_t *p = begin;
+ const uint8_t *const end = buffered_input_.end();
+
+ while (p != end) {
+ if (*p != kStartOfFrameMarker) {
+ ++p;
+ continue;
+ }
+
+ if (end - p < 7) {
+ break;
+ }
+
+ uint32_t length = uint32_t(p[1]) + 1;
+ if (end - p < 2 + length + 4) {
+ break;
+ }
+
+ uint32_t sent_crc;
+ memcpy(&sent_crc, p + 2 + length, sizeof(sent_crc));
+ const uint32_t calc_crc = crc32(0, p + 2, length);
+
+ if (sent_crc != calc_crc) {
+ ++p;
+ continue;
+ }
+
+ auto out = buffered_input_;
+ out.Trim(p + 2 - begin, end - (p + 2) - length);
+ buffered_input_.TrimBegin(p + 2 + length + 4 - begin);
+ return Sts(out);
+ }
+
+ buffered_input_.TrimBegin(p - begin);
+ return Sts(Nothing);
+}
+
+bool UnreliableFramer::InputEmpty() const {
+ return buffered_input_.length() == 0;
+}
+
+Optional<Slice> UnreliableFramer::SkipNoise() {
+ const uint8_t *const begin = buffered_input_.begin();
+ const uint8_t *p = begin;
+ const uint8_t *const end = buffered_input_.end();
+
+ if (p == end) {
+ return Nothing;
+ }
+ if (*p != kStartOfFrameMarker) {
+ return Nothing;
+ }
+ ++p;
+ while (p != end) {
+ if (*p != kStartOfFrameMarker) {
+ ++p;
+ continue;
+ }
+
+ if (end - p < 7) {
+ break;
+ }
+
+ uint32_t length = uint32_t(p[1]) + 1;
+ if (end - p < 2 + length + 4) {
+ break;
+ }
+
+ uint32_t sent_crc;
+ memcpy(&sent_crc, p + 2 + length, sizeof(sent_crc));
+ const uint32_t calc_crc = crc32(0, p + 2, length);
+
+ if (sent_crc != calc_crc) {
+ ++p;
+ continue;
+ }
+
+ break;
+ }
+
+ return buffered_input_.TakeUntilPointer(p);
+}
+
+Slice UnreliableFramer::Frame(Slice data) {
+ const auto length = data.length();
+ if (length == 0) {
+ return data;
+ }
+ assert(length <= 256);
+ return data.WithBorders(Border{2, 4}, [length](uint8_t *p) {
+ p[0] = kStartOfFrameMarker;
+ p[1] = length - 1;
+ auto *s = p + 2 + length;
+ const uint32_t crc = crc32(0, p + 2, length);
+ memcpy(s, &crc, sizeof(crc));
+ });
+}
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/unreliable_framer.h b/src/connectivity/overnet/lib/protocol/unreliable_framer.h
new file mode 100644
index 0000000..5f52fc4
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/unreliable_framer.h
@@ -0,0 +1,31 @@
+// 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.
+
+#pragma once
+
+#include "src/connectivity/overnet/lib/protocol/stream_framer.h"
+
+namespace overnet {
+
+// Framer that transports packets on an unreliable stream of bytes (assumes
+// bytes may be dropped, replicated, and/or mutated)
+class UnreliableFramer final : public StreamFramer {
+ public:
+ static constexpr uint8_t kStartOfFrameMarker = '\n';
+
+ UnreliableFramer();
+ ~UnreliableFramer();
+
+ void Push(Slice data) override;
+ StatusOr<Optional<Slice>> Pop() override;
+ bool InputEmpty() const override;
+ Optional<Slice> SkipNoise() override;
+
+ Slice Frame(Slice data) override;
+
+ private:
+ Slice buffered_input_;
+};
+
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/protocol/unreliable_framer_test.cc b/src/connectivity/overnet/lib/protocol/unreliable_framer_test.cc
new file mode 100644
index 0000000..3ecf349
--- /dev/null
+++ b/src/connectivity/overnet/lib/protocol/unreliable_framer_test.cc
@@ -0,0 +1,114 @@
+// 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 "src/connectivity/overnet/lib/protocol/unreliable_framer.h"
+
+#include <gtest/gtest.h>
+
+namespace overnet {
+namespace unreliable_framer_test {
+
+struct Param {
+ Slice input;
+ std::vector<Slice> output;
+};
+
+std::ostream& operator<<(std::ostream& out, const Param& param) {
+ out << param.input << " --> {";
+ bool first = true;
+ for (const auto& output : param.output) {
+ if (!first) {
+ out << ", ";
+ }
+ first = false;
+ out << output;
+ }
+ out << "}";
+ return out;
+}
+
+struct UnreliableFramerTest : public ::testing::TestWithParam<Param> {};
+
+TEST_P(UnreliableFramerTest, UnframesCorrectly_AtOnce) {
+ UnreliableFramer framer;
+ framer.Push(GetParam().input);
+ for (const auto& expect : GetParam().output) {
+ while (true) {
+ auto frame = framer.Pop();
+ ASSERT_TRUE(frame.is_ok()) << frame;
+ if (frame->has_value()) {
+ EXPECT_EQ(expect, **frame);
+ break; // from while loop
+ } else {
+ // No frame ready: skip any noise (simulates timeout), try again.
+ EXPECT_TRUE(framer.SkipNoise().has_value());
+ }
+ }
+ }
+ auto frame = framer.Pop();
+ ASSERT_TRUE(frame.is_ok());
+ ASSERT_FALSE(frame->has_value());
+}
+
+TEST_P(UnreliableFramerTest, UnframesCorrectly_OneByteAtATime) {
+ UnreliableFramer framer;
+ auto expect_it = GetParam().output.begin();
+ auto expect_end = GetParam().output.end();
+ for (auto c : GetParam().input) {
+ framer.Push(Slice::RepeatedChar(1, c));
+ auto frame = framer.Pop();
+ ASSERT_TRUE(frame.is_ok());
+ if (expect_it == expect_end) {
+ EXPECT_FALSE(frame->has_value());
+ } else if (frame->has_value()) {
+ EXPECT_EQ(*expect_it, **frame);
+ ++expect_it;
+ } else {
+ // nothing to do
+ }
+ }
+ while (expect_it != expect_end) {
+ EXPECT_TRUE(framer.SkipNoise().has_value());
+ while (expect_it != expect_end) {
+ auto frame = framer.Pop();
+ ASSERT_TRUE(frame.is_ok()) << frame;
+ if (frame->has_value()) {
+ EXPECT_EQ(*expect_it, **frame);
+ ++expect_it;
+ } else {
+ // No frame ready: skip any noise (simulates timeout), try again.
+ break; // from while loop
+ }
+ }
+ }
+ EXPECT_EQ(size_t(expect_it - GetParam().output.begin()),
+ GetParam().output.size());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ UnreliableFramerSuite, UnreliableFramerTest,
+ ::testing::Values(
+ // Simple correct frame
+ Param{Slice::FromContainer({'\n', 2, 'a', 'b', 'c', 0xc2, 0x41, 0x24,
+ 0x35}),
+ {Slice::FromContainer({'a', 'b', 'c'})}},
+ // Correct frame prefixed with noise, and suffixed with noise
+ Param{Slice::FromContainer({'h', 'e', 'l', 'l', 'o', '\n', 2, 'a', 'b',
+ 'c', 0xc2, 0x41, 0x24, 0x35, '\n'}),
+ {Slice::FromContainer({'a', 'b', 'c'})}},
+ // Badly formed frame (incorrect CRC)
+ Param{Slice::FromContainer({'\n', 2, 'a', 'b', 'c', 0xc2, 0x41, 0x00,
+ 0x35}),
+ {}},
+ // Correct frame prefixed with noise, and suffixed with noise, then a
+ // new frame
+ Param{Slice::FromContainer({'h', 'e', 'l', 'l', 'o', '\n', 2,
+ 'a', 'b', 'c', 0xc2, 0x41, 0x24, 0x35,
+ '\n', 'b', 'o', 'b', '\n', 2, 'a',
+ 'b', 'c', 0xc2, 0x41, 0x24, 0x35}),
+ {Slice::FromContainer({'a', 'b', 'c'}),
+ Slice::FromContainer({'a', 'b', 'c'})}}));
+
+} // namespace unreliable_framer_test
+} // namespace overnet
diff --git a/src/connectivity/overnet/lib/routing/router.cc b/src/connectivity/overnet/lib/routing/router.cc
index efbfbd7..4235cc9 100644
--- a/src/connectivity/overnet/lib/routing/router.cc
+++ b/src/connectivity/overnet/lib/routing/router.cc
@@ -215,7 +215,8 @@
// Set routing information for other links.
for (const auto& sl : selected_links) {
OVERNET_TRACE(DEBUG)
- << "Select: " << sl.first << " " << sl.second.link_id
+ << node_id() << " Select: dest=" << sl.first << " link"
+ << sl.second.target_node << "#" << sl.second.link_id
<< " (route_mss=" << sl.second.route_mss << ")";
auto it = owned_links_.find(
OwnedLabel{sl.second.target_node, sl.second.link_id});
@@ -312,6 +313,7 @@
void Router::RegisterLink(LinkPtr<> link) {
ScopedModule<Router> scoped_module(this);
auto status = link->GetLinkStatus();
+ OVERNET_TRACE(DEBUG) << node_id() << " RegisterLink: " << status;
assert(status.from == node_id());
owned_links_.emplace(OwnedLabel{status.to, status.local_id}, std::move(link));
auto target = status.to;
diff --git a/src/connectivity/overnet/lib/routing/router.h b/src/connectivity/overnet/lib/routing/router.h
index c719ba9..c4e20b3 100644
--- a/src/connectivity/overnet/lib/routing/router.h
+++ b/src/connectivity/overnet/lib/routing/router.h
@@ -33,14 +33,18 @@
namespace std {
template <>
struct hash<overnet::router_impl::LocalStreamId> {
- size_t operator()(const overnet::router_impl::LocalStreamId& id) const { return id.Hash(); }
+ size_t operator()(const overnet::router_impl::LocalStreamId& id) const {
+ return id.Hash();
+ }
};
} // namespace std
namespace overnet {
inline auto ForwardingPayloadFactory(Slice payload) {
- return [payload = std::move(payload)](auto args) mutable { return std::move(payload); };
+ return [payload = std::move(payload)](auto args) mutable {
+ return std::move(payload);
+ };
}
struct Message final {
@@ -49,7 +53,8 @@
TimeStamp received;
uint32_t mss = std::numeric_limits<uint32_t>::max();
- static Message SimpleForwarder(RoutableMessage msg, Slice payload, TimeStamp received) {
+ static Message SimpleForwarder(RoutableMessage msg, Slice payload,
+ TimeStamp received) {
return Message{std::move(msg), ForwardingPayloadFactory(payload), received};
}
};
@@ -82,7 +87,6 @@
};
Router(Timer* timer, NodeId node_id, bool allow_non_determinism);
-
virtual ~Router();
virtual void Close(Callback<void> quiesced);
@@ -90,8 +94,10 @@
// Forward a message to either ourselves or a link
void Forward(Message message);
// Register a (locally handled) stream into this Router
- Status RegisterStream(NodeId peer, StreamId stream_id, StreamHandler* stream_handler);
- Status UnregisterStream(NodeId peer, StreamId stream_id, StreamHandler* stream_handler);
+ Status RegisterStream(NodeId peer, StreamId stream_id,
+ StreamHandler* stream_handler);
+ Status UnregisterStream(NodeId peer, StreamId stream_id,
+ StreamHandler* stream_handler);
// Register a link to another router (usually on a different machine)
void RegisterLink(LinkPtr<> link);
@@ -101,7 +107,8 @@
void UpdateRoutingTable(
std::initializer_list<fuchsia::overnet::protocol::NodeStatus> node_status,
- std::initializer_list<fuchsia::overnet::protocol::LinkStatus> link_status) {
+ std::initializer_list<fuchsia::overnet::protocol::LinkStatus>
+ link_status) {
UpdateRoutingTable(std::move(node_status), std::move(link_status), false);
}
@@ -115,7 +122,8 @@
}
Optional<NodeId> SelectGossipPeer();
- void SendGossipUpdate(fuchsia::overnet::protocol::Peer_Proxy* peer, NodeId target);
+ void SendGossipUpdate(fuchsia::overnet::protocol::Peer_Proxy* peer,
+ NodeId target);
void ApplyGossipUpdate(fuchsia::overnet::protocol::NodeStatus node_status) {
UpdateRoutingTable({std::move(node_status)}, {}, false);
@@ -143,13 +151,16 @@
}
}
+ uint64_t GenerateLinkLabel() { return next_link_label_++; }
+
private:
Timer* const timer_;
const NodeId node_id_;
- void UpdateRoutingTable(std::initializer_list<fuchsia::overnet::protocol::NodeStatus> node_status,
- std::initializer_list<fuchsia::overnet::protocol::LinkStatus> link_status,
- bool flush_old_nodes);
+ void UpdateRoutingTable(
+ std::initializer_list<fuchsia::overnet::protocol::NodeStatus> node_status,
+ std::initializer_list<fuchsia::overnet::protocol::LinkStatus> link_status,
+ bool flush_old_nodes);
virtual void OnUnknownStream(NodeId peer, StreamId stream_id) {}
void MaybeStartPollingLinkChanges();
@@ -162,7 +173,8 @@
public:
StreamHolder(NodeId peer, StreamId id) : peer_(peer), stream_(id) {}
Status SetHandler(StreamHandler* handler);
- [[nodiscard]] bool HandleMessage(SeqNum seq, TimeStamp received, Slice payload);
+ [[nodiscard]] bool HandleMessage(SeqNum seq, TimeStamp received,
+ Slice payload);
Status ClearHandler(StreamHandler* handler);
void Close(Callback<void> quiesced) {
if (handler_ != nullptr)
@@ -209,7 +221,8 @@
if (it != links_.end())
return &it->second;
return &links_
- .emplace(std::piecewise_construct, std::forward_as_tuple(node_id),
+ .emplace(std::piecewise_construct,
+ std::forward_as_tuple(node_id),
std::forward_as_tuple(node_id))
.first->second;
}
@@ -219,9 +232,10 @@
if (it != streams_.end())
return &it->second;
return &streams_
- .emplace(std::piecewise_construct,
- std::forward_as_tuple(LocalStreamId{node_id, stream_id}),
- std::forward_as_tuple(node_id, stream_id))
+ .emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(LocalStreamId{node_id, stream_id}),
+ std::forward_as_tuple(node_id, stream_id))
.first->second;
}
@@ -232,7 +246,8 @@
uint64_t target_nodes_label;
bool operator==(const OwnedLabel& other) const {
- return target_node == other.target_node && target_nodes_label == other.target_nodes_label;
+ return target_node == other.target_node &&
+ target_nodes_label == other.target_nodes_label;
}
};
@@ -253,6 +268,8 @@
Optional<Timeout> poll_link_changes_timeout_;
Optional<Timeout> flush_old_nodes_timeout_;
fuchsia::overnet::protocol::NodeStatus own_node_status_;
+
+ uint64_t next_link_label_ = 1;
};
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/routing/routing_table.cc b/src/connectivity/overnet/lib/routing/routing_table.cc
index ce62af1..22786f1 100644
--- a/src/connectivity/overnet/lib/routing/routing_table.cc
+++ b/src/connectivity/overnet/lib/routing/routing_table.cc
@@ -133,9 +133,9 @@
}
for (const auto& m : changes.links) {
auto report_drop = [&m](const char* why) {
- OVERNET_TRACE(INFO) << "Drop link info: from=" << m.from << " to=" << m.to
- << " label=" << m.local_id << " version=" << m.version
- << ": " << why;
+ OVERNET_TRACE(DEBUG) << "Drop link info: from=" << m.from
+ << " to=" << m.to << " label=" << m.local_id
+ << " version=" << m.version << ": " << why;
};
// Cannot add a link if the relevant nodes are unknown.
auto from_node = nodes_.find(NodeId(m.from));
@@ -239,6 +239,13 @@
return SelectedLinks(); // Root node as yet unknown.
}
+ for (const auto& n : nodes_) {
+ OVERNET_TRACE(DEBUG) << n.first << " metrics\n" << n.second.status.metrics;
+ }
+ for (const auto& l : links_) {
+ OVERNET_TRACE(DEBUG) << l.first << " metrics\n" << l.second.status.metrics;
+ }
+
++path_finding_run_;
node_it->second.last_path_finding_run = path_finding_run_;
node_it->second.best_rtt = TimeDelta::Zero();
@@ -252,6 +259,15 @@
todo.PushBack(node);
};
+ auto node_id_of = [this](const Node* n) {
+ for (const auto& np : nodes_) {
+ if (&np.second == n) {
+ return np.first;
+ }
+ }
+ return NodeId(0);
+ };
+
enqueue(&node_it->second);
while (!todo.Empty()) {
@@ -261,17 +277,31 @@
if (link->status.version ==
fuchsia::overnet::protocol::METRIC_VERSION_TOMBSTONE)
continue;
- TimeDelta rtt =
+ TimeDelta rtt = std::min(
+ TimeDelta::FromHours(1),
src->best_rtt +
- (src->status.metrics.has_forwarding_time()
- ? TimeDelta::FromMicroseconds(
- src->status.metrics.forwarding_time())
- : TimeDelta::PositiveInf()) +
- (link->status.metrics.has_rtt()
- ? TimeDelta::FromMicroseconds(link->status.metrics.rtt())
- : TimeDelta::PositiveInf());
+ (src->status.metrics.has_forwarding_time()
+ ? TimeDelta::FromMicroseconds(
+ src->status.metrics.forwarding_time())
+ : TimeDelta::PositiveInf()) +
+ (link->status.metrics.has_rtt()
+ ? TimeDelta::FromMicroseconds(link->status.metrics.rtt())
+ : TimeDelta::PositiveInf()));
Node* dst = link->to_node;
// For now we order by RTT.
+ OVERNET_TRACE(DEBUG) << "RB[" << root_node_
+ << "]: src=" << node_id_of(src)
+ << " dst=" << node_id_of(dst)
+ << " dst->last_path_finding_run="
+ << dst->last_path_finding_run
+ << " path_finding_run=" << path_finding_run_
+ << " src->best_rtt=" << src->best_rtt
+ << " dst->best_rtt=" << dst->best_rtt
+ << " rtt=" << rtt << " src->mss=" << src->mss
+ << " link->mss="
+ << (link->status.metrics.has_mss()
+ ? link->status.metrics.mss()
+ : std::numeric_limits<uint32_t>::max());
if (dst->last_path_finding_run != path_finding_run_ ||
dst->best_rtt > rtt) {
dst->last_path_finding_run = path_finding_run_;
@@ -290,21 +320,23 @@
SelectedLinks selected_links;
for (node_it = nodes_.begin(); node_it != nodes_.end(); ++node_it) {
- if (node_it->second.last_path_finding_run != path_finding_run_) {
- continue; // Unreachable
- }
if (node_it->first == root_node_) {
continue;
}
+ if (node_it->second.last_path_finding_run != path_finding_run_) {
+ OVERNET_TRACE(DEBUG) << "RB[" << root_node_ << "]: " << node_it->first
+ << " unreachable";
+ continue; // Unreachable
+ }
Node* n = &node_it->second;
while (n->best_from->status.id != root_node_) {
n = n->best_from;
}
Link* link = n->best_link;
assert(link->status.from == root_node_);
- selected_links.emplace(
- node_it->first,
- SelectedLink{link->status.to, link->status.local_id, n->mss});
+ selected_links.emplace(node_it->first,
+ SelectedLink{link->status.to, link->status.local_id,
+ node_it->second.mss});
}
return selected_links;
@@ -312,12 +344,13 @@
uint64_t RoutingTable::SendUpdate(fuchsia::overnet::protocol::Peer_Proxy* peer,
Optional<NodeId> exclude_node) const {
- OVERNET_TRACE(DEBUG) << "SendUpdate";
+ OVERNET_TRACE(DEBUG) << root_node_ << " SendUpdate exclude=" << exclude_node;
std::lock_guard<std::mutex> mutex(shared_table_mu_);
std::unordered_set<NodeId> version_zero_nodes;
for (const auto& m : shared_node_status_) {
+ OVERNET_TRACE(DEBUG) << "Consider node: " << m;
if (m.version == 0) {
version_zero_nodes.insert(NodeId(m.id));
continue;
@@ -330,6 +363,7 @@
}
for (const auto& m : shared_link_status_) {
+ OVERNET_TRACE(DEBUG) << "Consider link: " << m;
if (NodeId(m.from) == exclude_node ||
version_zero_nodes.count(NodeId(m.from)) > 0 ||
version_zero_nodes.count(NodeId(m.to)) > 0) {
diff --git a/src/connectivity/overnet/lib/routing/routing_table.h b/src/connectivity/overnet/lib/routing/routing_table.h
index 505212a..4b18651 100644
--- a/src/connectivity/overnet/lib/routing/routing_table.h
+++ b/src/connectivity/overnet/lib/routing/routing_table.h
@@ -10,6 +10,7 @@
#include <condition_variable>
#include <iostream>
#include <mutex>
+#include <ostream>
#include <thread>
#include <tuple>
#include <unordered_map>
@@ -37,6 +38,10 @@
return a.from == b.from && a.to == b.to && a.link_label == b.link_label;
}
+inline std::ostream& operator<<(std::ostream& out, FullLinkLabel lbl) {
+ return out << lbl.from << "->" << lbl.to << "#" << lbl.link_label;
+}
+
} // namespace routing_table_impl
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/stats/BUILD.gn b/src/connectivity/overnet/lib/stats/BUILD.gn
index 8935cf7..097494f 100644
--- a/src/connectivity/overnet/lib/stats/BUILD.gn
+++ b/src/connectivity/overnet/lib/stats/BUILD.gn
@@ -31,12 +31,7 @@
}
stats("link") {}
-
-group("lib") {
- public_deps = [
- ":link",
- ]
-}
+stats("stream") {}
source_set("visitor") {
sources = [
diff --git a/src/connectivity/overnet/lib/stats/link.py b/src/connectivity/overnet/lib/stats/link.py
index 152f9fc..8d7c620 100755
--- a/src/connectivity/overnet/lib/stats/link.py
+++ b/src/connectivity/overnet/lib/stats/link.py
@@ -12,5 +12,28 @@
stats = [
statsc.Counter(name='incoming_packet_count'),
statsc.Counter(name='outgoing_packet_count'),
+
+ statsc.Counter(name='unseen_packets_marked_not_received'),
+ statsc.Counter(name='acks_sent'),
+ statsc.Counter(name='pure_acks_sent'),
+ statsc.Counter(name='tail_loss_probes_scheduled'),
+ statsc.Counter(name='tail_loss_probes_cancelled_because_requests_already_queued'),
+ statsc.Counter(name='tail_loss_probes_cancelled_because_probe_already_scheduled'),
+ statsc.Counter(name='tail_loss_probes_cancelled_after_timer_created'),
+
+ statsc.Counter(name='tail_loss_probe_scheduled_because_ack_required_soon_timer_expired'),
+ statsc.Counter(name='tail_loss_probe_scheduled_because_send_queue_is_empty'),
+
+ statsc.Counter(name='ack_not_required_historic_sequence'),
+ statsc.Counter(name='ack_not_required_frozen_sequence'),
+ statsc.Counter(name='ack_not_required_invalid_packet'),
+ statsc.Counter(name='ack_not_required_short_optional_run'),
+ statsc.Counter(name='ack_required_soon_ack_received'),
+ statsc.Counter(name='ack_required_soon_data_received'),
+ statsc.Counter(name='ack_required_soon_continue_partial_after_ack'),
+ statsc.Counter(name='ack_required_soon_all_acks_nacked'),
+ statsc.Counter(name='ack_required_immediately_due_to_nack'),
+ statsc.Counter(name='ack_required_immediately_due_to_partial_ack'),
+ statsc.Counter(name='ack_required_immediately_due_to_multiple_receives'),
]
)
diff --git a/src/connectivity/overnet/lib/stats/stream.py b/src/connectivity/overnet/lib/stats/stream.py
new file mode 100755
index 0000000..827e087
--- /dev/null
+++ b/src/connectivity/overnet/lib/stats/stream.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+# 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.
+
+import statsc
+
+statsc.compile(
+ name='Stream',
+ include='src/connectivity/overnet/lib/stats/stream.h',
+ stats = [
+ statsc.Counter(name='linearizer_reject_past_end_of_buffering'),
+ statsc.Counter(name='linearizer_empty_chunk'),
+ statsc.Counter(name='linearizer_fast_path_taken'),
+ statsc.Counter(name='linearizer_ignore_all_prior'),
+ statsc.Counter(name='linearizer_partial_ignore_begin'),
+ statsc.Counter(name='linearizer_new_pending_queue'),
+ statsc.Counter(name='linearizer_integrations'),
+ statsc.Counter(name='linearizer_integration_inserts'),
+ statsc.Counter(name='linearizer_integration_errors'),
+ statsc.Counter(name='linearizer_integration_coincident_shorter'),
+ statsc.Counter(name='linearizer_integration_coincident_longer'),
+ statsc.Counter(name='linearizer_integration_prior_longer'),
+ statsc.Counter(name='linearizer_integration_prior_partial'),
+ statsc.Counter(name='linearizer_integration_subsequent_splits'),
+ statsc.Counter(name='linearizer_integration_subsequent_covers'),
+ statsc.Counter(name='send_chunk_cancel_packet_too_small'),
+ statsc.Counter(name='send_chunk_split_packet_too_small'),
+ statsc.Counter(name='send_chunk_take_entire_chunk'),
+ statsc.Counter(name='send_chunk_nacked'),
+ statsc.Counter(name='send_chunk_push'),
+ ]
+)
diff --git a/src/connectivity/overnet/lib/vocabulary/ip_addr.cc b/src/connectivity/overnet/lib/vocabulary/ip_addr.cc
index a821c9b..3e4d70d 100644
--- a/src/connectivity/overnet/lib/vocabulary/ip_addr.cc
+++ b/src/connectivity/overnet/lib/vocabulary/ip_addr.cc
@@ -3,12 +3,27 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/vocabulary/ip_addr.h"
+
#include <arpa/inet.h>
#include <string.h>
+
#include <ostream>
namespace overnet {
+#ifndef __Fuchsia__
+Optional<IpAddr> IpAddr::Unix(const std::string& name) {
+ IpAddr out;
+ if (name.length() >= sizeof(out.unix.sun_path)) {
+ return Nothing;
+ }
+ memset(&out, 0, sizeof(out));
+ out.unix.sun_family = AF_UNIX;
+ memcpy(out.unix.sun_path, name.data(), name.length());
+ return out;
+}
+#endif
+
IpAddr IpAddr::AnyIpv4() {
IpAddr out;
memset(&out, 0, sizeof(out));
@@ -23,6 +38,21 @@
return out;
}
+socklen_t IpAddr::length() const {
+ switch (addr.sa_family) {
+ case AF_INET:
+ return sizeof(ipv4);
+ case AF_INET6:
+ return sizeof(ipv6);
+#ifndef __Fuchsia__
+ case AF_UNIX:
+ return SUN_LEN(&unix);
+#endif
+ default:
+ return sizeof(*this);
+ }
+}
+
Optional<IpAddr> IpAddr::WithPort(uint16_t port) const {
IpAddr out = *this;
switch (out.addr.sa_family) {
@@ -64,6 +94,10 @@
case AF_INET6:
inet_ntop(AF_INET6, &addr.ipv6.sin6_addr, dst, sizeof(dst));
return out << dst << "." << ntohs(addr.ipv6.sin6_port);
+#ifndef __Fuchsia__
+ case AF_UNIX:
+ return out << addr.unix.sun_path;
+#endif
default:
return out << "<<unknown address family " << addr.addr.sa_family << ">>";
}
@@ -87,6 +121,11 @@
add_value(addr.ipv6.sin6_addr);
add_value(addr.ipv6.sin6_port);
break;
+#ifndef __Fuchsia__
+ case AF_UNIX:
+ add_value(addr.unix.sun_path);
+ break;
+#endif
}
return out;
}
@@ -102,6 +141,10 @@
return a.ipv6.sin6_port == b.ipv6.sin6_port &&
0 == memcmp(&a.ipv6.sin6_addr, &b.ipv6.sin6_addr,
sizeof(a.ipv6.sin6_addr));
+#ifndef __Fuchsia__
+ case AF_UNIX:
+ return 0 == strcmp(a.unix.sun_path, b.unix.sun_path);
+#endif
}
}
if (auto a6 = a.AsIpv6(); a6.has_value()) {
diff --git a/src/connectivity/overnet/lib/vocabulary/ip_addr.h b/src/connectivity/overnet/lib/vocabulary/ip_addr.h
index 5eee649..3f1b83e 100644
--- a/src/connectivity/overnet/lib/vocabulary/ip_addr.h
+++ b/src/connectivity/overnet/lib/vocabulary/ip_addr.h
@@ -5,7 +5,11 @@
#pragma once
#include <netinet/in.h>
+#ifndef __Fuchsia__
+#include <sys/un.h>
+#endif
#include <iosfwd>
+
#include "src/connectivity/overnet/lib/vocabulary/optional.h"
namespace overnet {
@@ -14,12 +18,19 @@
sockaddr_in ipv4;
sockaddr_in6 ipv6;
sockaddr addr;
+#ifndef __Fuchsia__
+ sockaddr_un unix;
+ static Optional<IpAddr> Unix(const std::string& name);
+#endif
Optional<IpAddr> WithPort(uint16_t port) const;
static IpAddr AnyIpv4();
static IpAddr AnyIpv6();
+ const sockaddr* get() const { return &addr; }
+ socklen_t length() const;
+
Optional<IpAddr> AsIpv6() const;
uint16_t port() const {
switch (addr.sa_family) {
diff --git a/src/connectivity/overnet/lib/vocabulary/optional.h b/src/connectivity/overnet/lib/vocabulary/optional.h
index 6d86ce74..fd1208f 100644
--- a/src/connectivity/overnet/lib/vocabulary/optional.h
+++ b/src/connectivity/overnet/lib/vocabulary/optional.h
@@ -5,7 +5,9 @@
#pragma once
#include <assert.h>
+
#include <ostream>
+
#include "src/connectivity/overnet/lib/vocabulary/manual_constructor.h"
namespace overnet {
@@ -107,7 +109,8 @@
assert(set_);
return *storage_.get();
}
- operator bool() const { return set_; }
+ typedef bool Optional::*FakeBool;
+ operator FakeBool() const { return set_ ? &Optional::set_ : nullptr; }
bool has_value() const { return set_; }
T& value() {
assert(set_);
@@ -120,6 +123,14 @@
T* get() { return set_ ? storage_.get() : nullptr; }
const T* get() const { return set_ ? storage_.get() : nullptr; }
+ T* Force() {
+ if (!set_) {
+ set_ = true;
+ storage_.Init();
+ }
+ return get();
+ }
+
T Take() {
assert(set_);
set_ = false;
diff --git a/src/connectivity/overnet/lib/vocabulary/socket.cc b/src/connectivity/overnet/lib/vocabulary/socket.cc
index dbf03c8..7508b1d 100644
--- a/src/connectivity/overnet/lib/vocabulary/socket.cc
+++ b/src/connectivity/overnet/lib/vocabulary/socket.cc
@@ -3,7 +3,10 @@
// found in the LICENSE file.
#include "src/connectivity/overnet/lib/vocabulary/socket.h"
+
+#include <fcntl.h>
#include <unistd.h>
+
#include <cstring>
#include <sstream>
@@ -16,6 +19,18 @@
}
}
+Status Socket::Create(int family, int type, int option) {
+ *this = Socket(socket(family, type, option));
+ if (socket_ == -1) {
+ std::ostringstream msg;
+ msg << "Creating socket family=" << family << " type=" << type
+ << " option=" << option;
+ return Status(StatusCode::UNKNOWN, strerror(errno))
+ .WithContext(msg.str().c_str());
+ }
+ return Status::Ok();
+}
+
Status Socket::SetOptReusePort(bool reuse) {
return SetOpt(SOL_SOCKET, SO_REUSEPORT, reuse ? 1 : 0)
.WithContext(reuse ? "Enable SO_REUSEPORT" : "Disable SO_REUSEPORT");
@@ -26,27 +41,74 @@
return Status(StatusCode::INVALID_ARGUMENT, "Invalid socket");
}
if (setsockopt(socket_, level, opt, value, value_size) < 0) {
- return Status(StatusCode::INVALID_ARGUMENT, strerror(errno));
+ return Status(StatusCode::UNKNOWN, strerror(errno));
}
return Status::Ok();
}
+Status Socket::MutateFlags(std::function<int(int)> mut) {
+ if (!IsValid()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Invalid socket");
+ }
+ int flags = fcntl(socket_, F_GETFL);
+ if (flags == -1) {
+ return Status(StatusCode::UNKNOWN, strerror(errno))
+ .WithContext("fcntl(F_GETFL)");
+ }
+ flags = mut(flags);
+ if (0 != fcntl(socket_, F_SETFL, flags)) {
+ return Status(StatusCode::UNKNOWN, strerror(errno))
+ .WithContext("fcntl(F_SETFL)");
+ }
+ return Status::Ok();
+}
+
+Status Socket::SetNonBlocking(bool non_blocking) {
+ if (non_blocking) {
+ return MutateFlags([](int flags) { return flags | O_NONBLOCK; });
+ } else {
+ return MutateFlags([](int flags) { return flags ^ ~O_NONBLOCK; });
+ }
+}
+
Status Socket::Bind(IpAddr addr) {
if (!IsValid()) {
return Status(StatusCode::INVALID_ARGUMENT, "Invalid socket");
}
- if (bind(socket_, &addr.addr, sizeof(addr)) < 0) {
- auto err = errno;
- std::ostringstream msg;
- msg << "Bind to " << addr << " failed: " << strerror(err);
- return Status(StatusCode::INVALID_ARGUMENT, msg.str());
+#ifndef __Fuchsia__
+ // If we're using unix domain sockets, newest process always gets to handle
+ // the socket.
+ if (addr.addr.sa_family == AF_UNIX) {
+ unlink(addr.unix.sun_path);
+ }
+#endif
+ if (bind(socket_, addr.get(), addr.length()) < 0) {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithLazyContext([&] {
+ std::ostringstream out;
+ out << "bind(" << addr << ")";
+ return out.str();
+ });
+ }
+ return Status::Ok();
+}
+
+Status Socket::Connect(IpAddr addr) {
+ if (!IsValid()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Invalid socket");
+ }
+ if (0 != connect(socket_, addr.get(), addr.length())) {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithLazyContext([&] {
+ std::ostringstream out;
+ out << "connect(" << addr << ")";
+ return out.str();
+ });
}
return Status::Ok();
}
Status Socket::SendTo(Slice data, int flags, IpAddr dest) {
const auto result = sendto(socket_, data.begin(), data.length(), flags,
- &dest.addr, sizeof(dest));
+ dest.get(), dest.length());
if (result < 0) {
return Status(StatusCode::UNKNOWN, strerror(errno))
.WithContext("sendto failed");
@@ -75,4 +137,59 @@
return DataAndAddr{std::move(msg), std::move(source_address)};
}
+Status Socket::Listen() {
+ if (listen(socket_, 0) == 0) {
+ return Status::Ok();
+ } else if (errno == EINTR) {
+ return Listen();
+ } else {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithContext("listen");
+ }
+}
+
+StatusOr<Socket> Socket::Accept() {
+ int socket = accept(socket_, nullptr, nullptr);
+ if (socket >= 0) {
+ return Socket(socket);
+ } else if (errno == EAGAIN) {
+ return Status::Unavailable();
+ } else if (errno == EINTR) {
+ return Accept();
+ } else {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithContext("accept");
+ }
+}
+
+StatusOr<Slice> Socket::Write(Slice slice) {
+ if (auto n = write(socket_, slice.begin(), slice.length()); n >= 0) {
+ return slice.FromOffset(n);
+ } else if (errno == EAGAIN) {
+ return slice;
+ } else if (errno == EINTR) {
+ return Write(std::move(slice));
+ } else {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithContext("write");
+ }
+}
+
+StatusOr<Optional<Slice>> Socket::Read(size_t maximum_read_size) {
+ auto msg = Slice::WithInitializer(maximum_read_size, [](uint8_t*) {});
+ ssize_t n;
+ for (;;) {
+ n = read(socket_, const_cast<uint8_t*>(msg.begin()), msg.length());
+ if (n == 0) {
+ return Nothing;
+ } else if (n > 0) {
+ return msg.ToOffset(n);
+ } else if (errno == EAGAIN) {
+ return Slice();
+ } else if (errno == EINTR) {
+ continue;
+ } else {
+ return Status(StatusCode::UNKNOWN, strerror(errno)).WithContext("read");
+ }
+ abort();
+ }
+}
+
} // namespace overnet
diff --git a/src/connectivity/overnet/lib/vocabulary/socket.h b/src/connectivity/overnet/lib/vocabulary/socket.h
index 3b4091c..15feb60 100644
--- a/src/connectivity/overnet/lib/vocabulary/socket.h
+++ b/src/connectivity/overnet/lib/vocabulary/socket.h
@@ -5,7 +5,9 @@
#pragma once
#include <cstring>
+#include <functional>
#include <sstream>
+
#include "src/connectivity/overnet/lib/vocabulary/ip_addr.h"
#include "src/connectivity/overnet/lib/vocabulary/slice.h"
#include "src/connectivity/overnet/lib/vocabulary/status.h"
@@ -32,8 +34,12 @@
bool IsValid() const { return socket_ != -1; }
int get() const { return socket_; }
+ Status Create(int family, int type, int option);
Status SetOptReusePort(bool reuse);
+ Status MutateFlags(std::function<int(int)> mutator);
+ Status SetNonBlocking(bool non_blocking);
+
template <class T>
Status SetOpt(int level, int opt, T value) {
return SetOpt(level, opt, &value, sizeof(value));
@@ -42,6 +48,12 @@
Status SetOpt(int level, int opt, void* value, size_t value_size);
Status Bind(IpAddr addr);
Status SendTo(Slice data, int flags, IpAddr dest);
+ Status Listen();
+ Status Connect(IpAddr dest);
+ StatusOr<Socket> Accept();
+ // Returns the data that was not written, or error.
+ StatusOr<Slice> Write(Slice data);
+ StatusOr<Optional<Slice>> Read(size_t maximum_read_size);
struct DataAndAddr {
Slice data;
diff --git a/src/connectivity/overnet/overnetstack/omdp_nub.cc b/src/connectivity/overnet/overnetstack/omdp_nub.cc
index 4ac4c1b..7387591 100644
--- a/src/connectivity/overnet/overnetstack/omdp_nub.cc
+++ b/src/connectivity/overnet/overnetstack/omdp_nub.cc
@@ -29,9 +29,9 @@
if (incoming_.IsValid()) {
return;
}
- incoming_ = overnet::Socket(socket(AF_INET6, SOCK_DGRAM, 0));
auto status =
- incoming_.SetOptReusePort(true)
+ incoming_.Create(AF_INET6, SOCK_DGRAM, 0)
+ .Then([&] { return incoming_.SetOptReusePort(true); })
.Then([&] {
return incoming_.Bind(*overnet::IpAddr::AnyIpv6().WithPort(
kMulticastGroupAddr.port()));
diff --git a/src/connectivity/overnet/overnetstack/service.cc b/src/connectivity/overnet/overnetstack/service.cc
index 9bcffbb..a046161 100644
--- a/src/connectivity/overnet/overnetstack/service.cc
+++ b/src/connectivity/overnet/overnetstack/service.cc
@@ -21,8 +21,15 @@
using Peer = fuchsia::overnet::Peer;
app_->endpoint()->OnNodeDescriptionTableChange(
last_seen_version,
- overnet::Callback<void>(
- overnet::ALLOCATED_CALLBACK, [this, callback = std::move(callback)] {
+ overnet::StatusCallback(
+ overnet::ALLOCATED_CALLBACK, [this, callback = std::move(callback)](
+ const overnet::Status& status) {
+ if (status.is_error()) {
+ // Note: callback not called, but this case should imply that
+ // we're shutting down Overnet, so the associated channel should
+ // disappear also.
+ return;
+ }
std::vector<Peer> response;
auto new_version = app_->endpoint()->ForEachNodeDescription(
[&response, self_node = app_->endpoint()->node_id()](
diff --git a/src/connectivity/overnet/overnetstack/udp_nub.h b/src/connectivity/overnet/overnetstack/udp_nub.h
index ce7c7f2..db97b18 100644
--- a/src/connectivity/overnet/overnetstack/udp_nub.h
+++ b/src/connectivity/overnet/overnetstack/udp_nub.h
@@ -27,7 +27,7 @@
class UdpNub final : public UdpNubBase, public OvernetApp::Actor {
public:
explicit UdpNub(OvernetApp* app)
- : UdpNubBase(app->timer(), app->node_id()),
+ : UdpNubBase(app->endpoint()),
endpoint_(app->endpoint()),
timer_(app->timer()) {}
@@ -54,8 +54,6 @@
}
}
- overnet::Router* GetRouter() override { return endpoint_; }
-
void Publish(overnet::LinkPtr<> link) override {
overnet::NodeId node = overnet::NodeId(link->GetLinkStatus().to);
OVERNET_TRACE(DEBUG) << "NewLink: " << node << "\n";
diff --git a/src/connectivity/overnet/tools/BUILD.gn b/src/connectivity/overnet/tools/BUILD.gn
index 6cd74ed..0df3387 100644
--- a/src/connectivity/overnet/tools/BUILD.gn
+++ b/src/connectivity/overnet/tools/BUILD.gn
@@ -5,6 +5,7 @@
group("tools") {
testonly = true
deps = [
+ "ascendd",
"onet",
]
}
diff --git a/src/connectivity/overnet/tools/ascendd/BUILD.gn b/src/connectivity/overnet/tools/ascendd/BUILD.gn
new file mode 100644
index 0000000..f49490ef
--- /dev/null
+++ b/src/connectivity/overnet/tools/ascendd/BUILD.gn
@@ -0,0 +1,27 @@
+# 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.
+
+import("//build/fidl/fidl.gni")
+import("//build/package.gni")
+
+executable("bin") {
+ output_name = "ascendd"
+
+ sources = [ "ascendd.cc" ]
+ deps = [
+ "//src/connectivity/overnet/lib/embedded:basic_embedded",
+ "//src/connectivity/overnet/lib/embedded:stream_server",
+ "//src/connectivity/overnet/lib/embedded:udp_nub",
+ "//src/connectivity/overnet/lib/embedded:omdp_nub",
+ "//src/connectivity/overnet/lib/protocol:reliable_framer",
+ "//src/connectivity/overnet/lib/protocol:unreliable_framer",
+ "//third_party/gflags",
+ ]
+}
+
+group("ascendd") {
+ deps = [
+ ":bin(${host_toolchain})",
+ ]
+}
diff --git a/src/connectivity/overnet/tools/ascendd/ascendd.cc b/src/connectivity/overnet/tools/ascendd/ascendd.cc
new file mode 100644
index 0000000..721a172
--- /dev/null
+++ b/src/connectivity/overnet/tools/ascendd/ascendd.cc
@@ -0,0 +1,46 @@
+// 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 <gflags/gflags.h>
+
+#include "src/connectivity/overnet/lib/embedded/basic_overnet_embedded.h"
+#include "src/connectivity/overnet/lib/embedded/omdp_nub.h"
+#include "src/connectivity/overnet/lib/embedded/stream_server.h"
+#include "src/connectivity/overnet/lib/embedded/udp_nub.h"
+#include "src/connectivity/overnet/lib/protocol/reliable_framer.h"
+
+DEFINE_bool(udp, false, "Support Overnet over UDP");
+DEFINE_bool(omdp, true,
+ "Support OMDP discovery protocol for UDP communications");
+DEFINE_string(unix_socket, "/tmp/ascendd.socket",
+ "UNIX domain socket path for ascendd Overnet server");
+DEFINE_string(verbosity, "INFO", "Verbosity level");
+DEFINE_validator(
+ verbosity, +[](const char* flag_name, const std::string& value) {
+ return overnet::SeverityFromString(value).has_value();
+ });
+
+int main(int argc, char** argv) {
+ gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+ overnet::ScopedSeverity trace_severity(
+ *overnet::SeverityFromString(FLAGS_verbosity));
+
+ overnet::BasicOvernetEmbedded overnet_embedded;
+ overnet::StreamServer<overnet::ReliableFramer> stream_server(
+ &overnet_embedded, *overnet::IpAddr::Unix(FLAGS_unix_socket));
+
+ // Optional services
+ overnet::Optional<overnet::UdpNub> udp_nub;
+ overnet::Optional<overnet::OmdpNub> omdp_nub;
+
+ if (FLAGS_udp) {
+ udp_nub.Reset(&overnet_embedded);
+ }
+ if (udp_nub.has_value() && FLAGS_omdp) {
+ omdp_nub.Reset(&overnet_embedded, udp_nub.get());
+ }
+
+ return overnet_embedded.Run();
+}