From 6d5b4fde9bdae025238b9b2873014f7a1dc47364 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Fri, 1 Dec 2017 13:55:46 +0100 Subject: [PATCH 1/6] added float16 --- quicfloat.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++ quicfloat_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 quicfloat.go create mode 100644 quicfloat_test.go diff --git a/quicfloat.go b/quicfloat.go new file mode 100644 index 0000000..f91ce18 --- /dev/null +++ b/quicfloat.go @@ -0,0 +1,51 @@ +/* + * Based of the "half" package. See https://github.com/h2so5/half + */ + +package minq + +import "math" + +//TODO(ekr@rtfm.com) At the moment this is just a IEEE 754 float16 + +// A QuicFloat16 represents a 16-bit floating point number. +type QuicFloat16 uint16 + +// NewQuicFloat16 allocates and returns a new Float16 set to f. +func NewQuicFloat16(f float32) QuicFloat16 { + i := math.Float32bits(f) + sign := uint16((i >> 31) & 0x1) + exp := (i >> 23) & 0xff + exp16 := int16(exp) - 127 + 15 + frac := uint16(i>>13) & 0x3ff + if exp == 0 { + exp16 = 0 + } else if exp == 0xff { + exp16 = 0x1f + } else { + if exp16 > 0x1e { + exp16 = 0x1f + frac = 0 + } else if exp16 < 0x01 { + exp16 = 0 + frac = 0 + } + } + f16 := (sign << 15) | uint16(exp16<<10) | frac + return QuicFloat16(f16) +} + +// Float32 returns the float32 representation of f. +func (f QuicFloat16) Float32() float32 { + sign := uint32((f >> 15) & 0x1) + exp := (f >> 10) & 0x1f + exp32 := uint32(exp) + 127 - 15 + if exp == 0 { + exp32 = 0 + } else if exp == 0x1f { + exp32 = 0xff + } + frac := uint32(f & 0x3ff) + i := (sign << 31) | (exp32 << 23) | (frac << 13) + return math.Float32frombits(i) +} diff --git a/quicfloat_test.go b/quicfloat_test.go new file mode 100644 index 0000000..5ee12c4 --- /dev/null +++ b/quicfloat_test.go @@ -0,0 +1,46 @@ +/* + * Based of the "half" package. See https://github.com/h2so5/half + */ + +package minq + +import ( + "math" + "testing" +) + +func getFloatTable() map[QuicFloat16]float32 { + table := map[QuicFloat16]float32{ + 0x3c00: 1, + 0x4000: 2, + 0xc000: -2, + 0x7bfe: 65472, + 0x7bff: 65504, + 0xfbff: -65504, + 0x0000: 0, + 0x8000: float32(math.Copysign(0, -1)), + 0x7c00: float32(math.Inf(1)), + 0xfc00: float32(math.Inf(-1)), + 0x5b8f: 241.875, + 0x48c8: 9.5625, + } + return table +} + +func TestFloat32(t *testing.T) { + for k, v := range getFloatTable() { + f := k.Float32() + if f != v { + t.Errorf("ToFloat32(%d) = %f, want %f.", k, f, v) + } + } +} + +func TestNewQuicFloat16(t *testing.T) { + for k, v := range getFloatTable() { + i := NewQuicFloat16(v) + if i != k { + t.Errorf("FromFloat32(%f) = %d, want %d.", v, i, k) + } + } +} From a4e9b0ee2385915c2050ebfa2226088947c21786 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Fri, 1 Dec 2017 14:33:12 +0100 Subject: [PATCH 2/6] adding 'delay' field to ack frames --- connection.go | 2 +- frame.go | 9 +++++++-- frame_test.go | 12 ++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/connection.go b/connection.go index b329225..88cdd3b 100644 --- a/connection.go +++ b/connection.go @@ -593,7 +593,7 @@ func (c *Connection) sendOnStream(streamId uint32, data []byte) error { func (c *Connection) makeAckFrame(acks ackRanges, left int) (*frame, int, error) { c.log(logTypeConnection, "Making ack frame, room=%d", left) - af, rangesSent, err := newAckFrame(acks, left) + af, rangesSent, err := newAckFrame(c.recvd, acks, left) if err != nil { c.log(logTypeConnection, "Couldn't prepare ACK frame %v", err) return nil, 0, err diff --git a/frame.go b/frame.go index fc56e10..7e2bb31 100644 --- a/frame.go +++ b/frame.go @@ -392,7 +392,7 @@ func (f ackFrame) AckBlockSection__length() uintptr { return uintptr(f.NumBlocks) * (1 + f.AckBlockLength__length()) } -func newAckFrame(rs ackRanges, left int) (*frame, int, error) { +func newAckFrame(recvd *recvdPackets, rs ackRanges, left int) (*frame, int, error) { if left < 16 { return nil, 0, nil } @@ -411,8 +411,13 @@ func newAckFrame(rs ackRanges, left int) (*frame, int, error) { f.LargestAcknowledged = rs[0].lastPacket f.AckBlockLength = rs[0].count - 1 last := f.LargestAcknowledged - f.AckBlockLength - f.AckDelay = 0 + largestAckData, ok := recvd.packets[f.LargestAcknowledged] + /* Should always be there. Packets only get removed after being set to ack2, + * which means we should not be acking it again */ + assert(ok) + ackDelayMicros := float32(time.Since(largestAckData.t).Nanoseconds())/1e3 + f.AckDelay = uint16(NewQuicFloat16(ackDelayMicros)) addedRanges := 1 // SECOND, add the remaining ACK blocks that fit and that we have diff --git a/frame_test.go b/frame_test.go index f329d2a..d19e6ed 100644 --- a/frame_test.go +++ b/frame_test.go @@ -9,7 +9,11 @@ import ( func TestAckFrameOneRange(t *testing.T) { ar := []ackRange{{0xdeadbeef, 2}} - f, _, err := newAckFrame(ar, 21) + recvd := newRecvdPackets(logf) + recvd.init(ar[0].lastPacket) + recvd.packetSetReceived(ar[0].lastPacket, false, false) + + f, _, err := newAckFrame(recvd, ar, 21) assertNotError(t, err, "Couldn't make ack frame") err = f.encode() @@ -25,7 +29,11 @@ func TestAckFrameOneRange(t *testing.T) { func TestAckFrameTwoRanges(t *testing.T) { ar := []ackRange{{0xdeadbeef, 2}, {0xdeadbee0, 1}} - f, _, err := newAckFrame(ar, 26) + recvd := newRecvdPackets(logf) + recvd.init(ar[0].lastPacket) + recvd.packetSetReceived(ar[0].lastPacket, false, false) + + f, _, err := newAckFrame(recvd, ar, 26) assertNotError(t, err, "Couldn't make ack frame") err = f.encode() From 0113776ece7e748ca7d0722bf593768463bb4ce3 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Fri, 1 Dec 2017 15:22:27 +0100 Subject: [PATCH 3/6] added RTT estimate --- congestion.go | 27 +++++++++++++++++++-------- connection.go | 6 +++++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/congestion.go b/congestion.go index 56c98a8..2157a95 100644 --- a/congestion.go +++ b/congestion.go @@ -82,7 +82,7 @@ type CongestionControllerIetf struct { largestAckedPacket uint64 // largestRtt time.Duration smoothedRtt time.Duration - rttVar float32 + rttVar time.Duration reorderingThreshold int timeReorderingFraction float32 lossTime time.Time @@ -115,7 +115,7 @@ func (cc *CongestionControllerIetf) onPacketSent(pn uint64, isAckOnly bool, sent // acks is received to be a sorted list, where the largest packet numbers are at the beginning -func(cc *CongestionControllerIetf) onAckReceived(acks ackRanges, delay time.Duration){ +func(cc *CongestionControllerIetf) onAckReceived(acks ackRanges, ackDelay time.Duration){ // keep track of largest packet acked overall if acks[0].lastPacket > cc.largestAckedPacket { @@ -125,11 +125,11 @@ func(cc *CongestionControllerIetf) onAckReceived(acks ackRanges, delay time.Dura // If the largest acked is newly acked update rtt _, present := cc.sentPackets[acks[0].lastPacket] if present { - //TODO(ekr@rtfm.com) RTT stuff - //largestRtt = time.Now - cc.sentPackets[acks[0].lastPacket].txTime - //if (latestRtt > delay){ - // latestRtt -= delay - // cc.updateRtt(latestRtt) + latestRtt := time.Since(cc.sentPackets[acks[0].lastPacket].txTime) + if (latestRtt > ackDelay){ + latestRtt -= ackDelay + cc.updateRtt(latestRtt) + } } // find and proccess newly acked packets @@ -154,7 +154,18 @@ func (cc *CongestionControllerIetf) setLostPacketHandler(handler func(pn uint64) func(cc *CongestionControllerIetf) updateRtt(latestRtt time.Duration){ - //TODO(ekr@rtfm.com) + if (cc.smoothedRtt == 0){ + cc.smoothedRtt = latestRtt + cc.rttVar = time.Duration(int64(latestRtt) / 2) + } else { + rttDelta := cc.smoothedRtt - latestRtt; + if rttDelta < 0 { + rttDelta = -rttDelta + } + cc.rttVar = time.Duration(3/4 * int64(cc.rttVar) + 1/4 * int64(rttDelta)) + cc.smoothedRtt = time.Duration(7/8 * int64(cc.smoothedRtt) + 1/8 * int64(latestRtt)) + } + cc.conn.log(logTypeCongestion, "New RTT estimate: %v, variance: %v", cc.smoothedRtt, cc.rttVar) } func(cc *CongestionControllerIetf) onPacketAcked(pn uint64){ diff --git a/connection.go b/connection.go index 88cdd3b..48e2344 100644 --- a/connection.go +++ b/connection.go @@ -1560,6 +1560,10 @@ func (c *Connection) processAckFrame(f *ackFrame, protected bool) error { end := f.LargestAcknowledged start := end - f.AckBlockLength + // Decode ACK Delay + ackDelayMicros := QuicFloat16(f.AckDelay).Float32() + ackDelay := time.Duration(ackDelayMicros * 1e3) + // Process the First ACK Block c.log(logTypeAck, "%s: processing ACK range %x-%x", c.label(), start, end) c.processAckRange(start, end, protected) @@ -1593,7 +1597,7 @@ func (c *Connection) processAckFrame(f *ackFrame, protected bool) error { receivedAcks = append(receivedAcks, ackRange{end, end - start + 1}) } - c.congestion.onAckReceived(receivedAcks, 0) + c.congestion.onAckReceived(receivedAcks, ackDelay) return nil } From 7cf101e92a5e84dae880357bd1dc0e66957cdc57 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Fri, 1 Dec 2017 15:32:35 +0100 Subject: [PATCH 4/6] added TCP style RTT estimates --- congestion.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/congestion.go b/congestion.go index 2157a95..505a26d 100644 --- a/congestion.go +++ b/congestion.go @@ -83,6 +83,8 @@ type CongestionControllerIetf struct { // largestRtt time.Duration smoothedRtt time.Duration rttVar time.Duration + smoothedRttTcp time.Duration + rttVarTcp time.Duration reorderingThreshold int timeReorderingFraction float32 lossTime time.Time @@ -168,6 +170,22 @@ func(cc *CongestionControllerIetf) updateRtt(latestRtt time.Duration){ cc.conn.log(logTypeCongestion, "New RTT estimate: %v, variance: %v", cc.smoothedRtt, cc.rttVar) } +func(cc *CongestionControllerIetf) updateRttTcp(latestRtt time.Duration){ + if (cc.smoothedRttTcp == 0){ + cc.smoothedRttTcp = latestRtt + cc.rttVarTcp = time.Duration(int64(latestRtt) / 2) + } else { + rttDelta := cc.smoothedRttTcp - latestRtt; + if rttDelta < 0 { + rttDelta = -rttDelta + } + cc.rttVarTcp = time.Duration(3/4 * int64(cc.rttVarTcp) + 1/4 * int64(rttDelta)) + cc.smoothedRttTcp = time.Duration(7/8 * int64(cc.smoothedRttTcp) + 1/8 * int64(latestRtt)) + } + cc.conn.log(logTypeCongestion, "New RTT(TCP) estimate: %v, variance: %v", cc.smoothedRttTcp, cc.rttVarTcp) +} + + func(cc *CongestionControllerIetf) onPacketAcked(pn uint64){ cc.onPacketAckedCC(pn) //TODO(ekr@rtfm.com) some RTO stuff here @@ -280,6 +298,8 @@ func newCongestionControllerIetf(conn *Connection) *CongestionControllerIetf{ 0, // largestAckedPacket 0, // smoothedRtt 0, // rttVar + 0, // smoothedRttTcp + 0, // rttVarTcp kReorderingThreshold, // reorderingThreshold math.MaxFloat32, // timeReorderingFraction time.Unix(0,0), // lossTime From 6a085d97ff6b57cdc0a0022f69c135cb1042e287 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Fri, 1 Dec 2017 15:55:19 +0100 Subject: [PATCH 5/6] fix operation order bug --- congestion.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/congestion.go b/congestion.go index 505a26d..248a27d 100644 --- a/congestion.go +++ b/congestion.go @@ -128,10 +128,13 @@ func(cc *CongestionControllerIetf) onAckReceived(acks ackRanges, ackDelay time.D _, present := cc.sentPackets[acks[0].lastPacket] if present { latestRtt := time.Since(cc.sentPackets[acks[0].lastPacket].txTime) + cc.conn.log(logTypeCongestion, "latestRtt: %v, ackDelay: %v", latestRtt, ackDelay) + cc.updateRttTcp(latestRtt) + if (latestRtt > ackDelay){ latestRtt -= ackDelay - cc.updateRtt(latestRtt) } + cc.updateRtt(latestRtt) } // find and proccess newly acked packets @@ -164,8 +167,8 @@ func(cc *CongestionControllerIetf) updateRtt(latestRtt time.Duration){ if rttDelta < 0 { rttDelta = -rttDelta } - cc.rttVar = time.Duration(3/4 * int64(cc.rttVar) + 1/4 * int64(rttDelta)) - cc.smoothedRtt = time.Duration(7/8 * int64(cc.smoothedRtt) + 1/8 * int64(latestRtt)) + cc.rttVar = time.Duration(int64(cc.rttVar) * 3/4 + int64(rttDelta) * 1/4) + cc.smoothedRtt = time.Duration(int64(cc.smoothedRtt) * 7/8 + int64(latestRtt) * 1/8) } cc.conn.log(logTypeCongestion, "New RTT estimate: %v, variance: %v", cc.smoothedRtt, cc.rttVar) } @@ -179,8 +182,8 @@ func(cc *CongestionControllerIetf) updateRttTcp(latestRtt time.Duration){ if rttDelta < 0 { rttDelta = -rttDelta } - cc.rttVarTcp = time.Duration(3/4 * int64(cc.rttVarTcp) + 1/4 * int64(rttDelta)) - cc.smoothedRttTcp = time.Duration(7/8 * int64(cc.smoothedRttTcp) + 1/8 * int64(latestRtt)) + cc.rttVarTcp = time.Duration(int64(cc.rttVarTcp) * 3/4 + int64(rttDelta) * 3/4) + cc.smoothedRttTcp = time.Duration(int64(cc.smoothedRttTcp) * 7/8 + int64(latestRtt) * 1/8) } cc.conn.log(logTypeCongestion, "New RTT(TCP) estimate: %v, variance: %v", cc.smoothedRttTcp, cc.rttVarTcp) } From 4ef6dd401321f52acb6480719b5ceed3ca6f4536 Mon Sep 17 00:00:00 2001 From: Piet De Vaere Date: Sun, 3 Dec 2017 11:18:25 +0100 Subject: [PATCH 6/6] remove magic constant --- frame.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame.go b/frame.go index 7e2bb31..40444d5 100644 --- a/frame.go +++ b/frame.go @@ -393,7 +393,7 @@ func (f ackFrame) AckBlockSection__length() uintptr { } func newAckFrame(recvd *recvdPackets, rs ackRanges, left int) (*frame, int, error) { - if left < 16 { + if left < kAckHeaderLength { return nil, 0, nil } logf(logTypeFrame, "Making ACK frame %v", rs)