blob: 0a7738af64f31d21c59528f1a0d0b3cd79a4d1e5 [file] [log] [blame]
// Copyright 2018 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 <assert.h>
#include <tuple>
#include <vector>
#include "garnet/lib/overnet/environment/trace.h"
#include "garnet/lib/overnet/protocol/varint.h"
#include "garnet/lib/overnet/vocabulary/slice.h"
#include "garnet/lib/overnet/vocabulary/status.h"
namespace overnet {
class AckFrame {
struct Block {
uint64_t acks;
uint64_t nacks;
bool operator==(const Block& other) const {
return acks == other.acks && nacks == other.nacks;
}
};
using Brit = std::vector<Block>::const_reverse_iterator;
public:
class Writer {
public:
explicit Writer(const AckFrame* ack_frame);
size_t wire_length() const { return wire_length_; }
uint8_t* Write(uint8_t* out) const;
private:
const AckFrame* const ack_frame_;
const size_t wire_length_;
};
AckFrame(uint64_t ack_to_seq, uint64_t ack_delay_us)
: partial_(false), ack_to_seq_(ack_to_seq), ack_delay_us_(ack_delay_us) {
assert(ack_to_seq_ > 0);
}
AckFrame(uint64_t ack_to_seq, uint64_t ack_delay_us,
std::initializer_list<uint64_t> nack_seqs)
: partial_(false), ack_to_seq_(ack_to_seq), ack_delay_us_(ack_delay_us) {
assert(ack_to_seq_ > 0);
for (auto n : nack_seqs) {
AddNack(n);
}
}
AckFrame(const AckFrame&) = delete;
AckFrame& operator=(const AckFrame&) = delete;
AckFrame(AckFrame&& other)
: partial_(other.partial_),
ack_to_seq_(other.ack_to_seq_),
ack_delay_us_(other.ack_delay_us_),
blocks_(std::move(other.blocks_)),
last_nack_(other.last_nack_) {}
AckFrame& operator=(AckFrame&& other) {
partial_ = other.partial_;
ack_to_seq_ = other.ack_to_seq_;
ack_delay_us_ = other.ack_delay_us_;
blocks_ = std::move(other.blocks_);
last_nack_ = other.last_nack_;
return *this;
}
void AddNack(uint64_t seq) {
assert(ack_to_seq_ > 0);
assert(seq <= ack_to_seq_);
assert(seq > 0);
if (!blocks_.empty()) {
assert(seq < last_nack_);
if (seq == last_nack_ - 1) {
blocks_.back().nacks++;
} else {
blocks_.emplace_back(Block{last_nack_ - seq - 1, 1});
}
} else {
blocks_.emplace_back(Block{ack_to_seq_ - seq, 1});
}
last_nack_ = seq;
}
static StatusOr<AckFrame> Parse(Slice slice);
friend bool operator==(const AckFrame& a, const AckFrame& b) {
if (std::tie(a.ack_to_seq_, a.ack_delay_us_, a.blocks_, a.partial_) !=
std::tie(b.ack_to_seq_, b.ack_delay_us_, b.blocks_, b.partial_)) {
return false;
}
if (!a.blocks_.empty()) {
return a.last_nack_ == b.last_nack_;
}
return true;
}
uint64_t ack_to_seq() const { return ack_to_seq_; }
uint64_t ack_delay_us() const { return ack_delay_us_; }
bool partial() const { return partial_; }
class NackSeqs {
public:
NackSeqs(const AckFrame* ack_frame) : ack_frame_(ack_frame) {}
class Iterator {
public:
Iterator(Brit brit, uint64_t base) : brit_(brit), base_(base) {}
bool operator!=(const Iterator& other) const {
return brit_ != other.brit_ || base_ != other.base_ ||
nack_ != other.nack_;
}
void operator++() {
nack_++;
if (nack_ == brit_->nacks) {
base_ += brit_->nacks + brit_->acks;
++brit_;
nack_ = 0;
}
}
uint64_t operator*() const { return base_ + nack_; }
private:
Brit brit_;
uint64_t base_;
uint64_t nack_ = 0;
};
std::vector<uint64_t> AsVector() const {
std::vector<uint64_t> out;
for (auto n : *this) {
out.push_back(n);
}
return out;
}
Iterator begin() const {
if (ack_frame_->blocks_.empty()) {
return end();
}
return Iterator(ack_frame_->blocks_.rbegin(), ack_frame_->last_nack_);
}
Iterator end() const {
return Iterator(ack_frame_->blocks_.rend(), ack_frame_->ack_to_seq_ + 1);
}
private:
const AckFrame* ack_frame_;
};
NackSeqs nack_seqs() const { return NackSeqs(this); }
// Move ack_to_seq back in time such that the total ack frame will fit
// within mss. DelayFn is a function uint64_t -> TimeDelta that returns the
// ack delay (in microseconds) for a given sequence number.
template <class DelayFn>
void AdjustForMSS(uint32_t mss, DelayFn delay_fn) {
while (!blocks_.empty() && WrittenLength() > mss) {
partial_ = true;
auto& block0_acks = blocks_[0].acks;
auto& block0_nacks = blocks_[0].nacks;
if (block0_acks > 0) {
auto new_acks = varint::SmallerRecordedNumber(block0_acks);
OVERNET_TRACE(DEBUG) << "Trim too long ack (" << WrittenLength()
<< " > " << mss << " by moving ack " << ack_to_seq_
<< " to shorter first ack block length "
<< (ack_to_seq_ - block0_acks + new_acks);
ack_to_seq_ -= (block0_acks - new_acks);
block0_acks = new_acks;
} else {
assert(block0_nacks > 0);
auto new_nacks = varint::SmallerRecordedNumber(block0_nacks);
if (new_nacks == 0) {
OVERNET_TRACE(DEBUG)
<< "Trim too long ack (" << WrittenLength() << " > " << mss
<< " by eliminating first block and moving first ack to "
<< (ack_to_seq_ - block0_nacks + new_nacks);
ack_to_seq_ -= (block0_nacks - new_nacks);
blocks_.erase(blocks_.begin());
} else {
OVERNET_TRACE(DEBUG)
<< "Trim too long ack (" << WrittenLength() << " > " << mss
<< " by moving ack " << ack_to_seq_
<< " to shorter first nack block length "
<< (ack_to_seq_ - block0_nacks + new_nacks);
ack_to_seq_ -= (block0_nacks - new_nacks);
block0_nacks = new_nacks;
}
}
}
}
private:
uint64_t DelayAndFlags() const;
uint64_t WrittenLength() const;
// Flag indicating that this ack is only a partial acknowledgement, and
// there's more to come.
bool partial_ = false;
// All messages with sequence number prior to ack_to_seq_ are implicitly
// acknowledged.
uint64_t ack_to_seq_;
// How long between receiving ack_delay_seq_ and generating this data
// structure.
uint64_t ack_delay_us_;
// From ack_to_seq working back we record blocks. A block contains some number
// of acks followed by some number of nacks.
std::vector<Block> blocks_;
uint64_t last_nack_;
};
std::ostream& operator<<(std::ostream& out, const AckFrame& ack_frame);
} // namespace overnet