Full Code of lithdew/reliable for AI

master 0afae4e9ed8a cached
19 files
57.8 KB
19.1k tokens
134 symbols
1 requests
Download .txt
Repository: lithdew/reliable
Branch: master
Commit: 0afae4e9ed8a
Files: 19
Total size: 57.8 KB

Directory structure:
gitextract_f9yxkokx/

├── .gitignore
├── LICENSE
├── README.md
├── conn.go
├── conn_test.go
├── endpoint.go
├── endpoint_test.go
├── error.go
├── examples/
│   ├── basic/
│   │   └── main.go
│   └── benchmark/
│       └── main.go
├── fuzz.go
├── go.mod
├── go.sum
├── options.go
├── packet.go
├── packet_test.go
├── pool.go
├── protocol.go
└── protocol_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea/
corpus/
crashers/
suppressions/
reliable-fuzz.zip


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Kenta Iwasaki <kenta@lithdew.net>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# reliable

[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](LICENSE)
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/lithdew/reliable)
[![Discord Chat](https://img.shields.io/discord/697002823123992617)](https://discord.gg/HZEbkeQ)

**reliable** is a reliability layer for UDP connections in Go.

With only 9 bytes of packet overhead at most, what **reliable** does for your UDP-based application is:

1. handle acknowledgement over the recipient of packets you sent,
2. handle sending acknowledgements when too many are being buffered up,
3. handle resending sent packets whose recipient hasn't been acknowledged after some timeout, and
4. handle stopping/buffering up packets to be sent when the recipients read buffer is suspected to be full.

** This project is still a WIP! Scrub through the source code, write some unit tests, help out with documentation, or open up a Github issue if you would like to help out or have any questions!

## Protocol

### Packet Header

**reliable** uses the same packet header layout described in [`networkprotocol/reliable.io`](https://github.com/networkprotocol/reliable.io).

All packets start with a single byte (8 bits) representing 8 different flags. Packets are sequential, and are numbered using an unsigned 16-bit integer included in the packet header unless the packet is marked to be unreliable.

Packet acknowledgements (ACKs) are redundantly included in every sent packet using a total of 5 bytes: two bytes representing an unsigned 16-bit packet sequence number (ack), and three bytes representing a 32-bit bitfield (ackBits).

The packet header layout, much like [`networkprotocol/reliable.io`](https://github.com/networkprotocol/reliable.io), is delta-encoded and RLE-encoded to reduce the size overhead per packet.

### Packet ACKs

Given a packet we have just received from our peer, for each set bit (i) in the bitfield (ackBits), we mark a packet we have sent to be acknowledged if its sequence number is (ack - i).

In the case of peer A sending packets to B, with B not sending any packets at all to A, B will send an empty packet for every 32 packets received from A so that A will be aware that B has acknowledged its packets.

More explicitly, a counter (lui) is maintained representing the last consecutive packet sequence number that we have received whose acknowledgement we have told to our peer about.

For example, if (lui) is 0, and we have sent acknowledgements for packets whose sequence numbers are 2, 3, 4, and 6, and we have then acknowledged packet sequence number 1, then lui would be 4.

Upon updating (lui), if the next 32 consecutive sequence numbers are sequence numbers of packets we have previously received, we will increment (lui) by 32 and send a single empty packet containing the following packet acknowledgements: (ack=lui+31, ackBits=[lui,lui+31]).

### Packet Buffering

Two fixed-sized sequence buffers are maintained for packets that we have sent (wq), and packets that we have received (rq). The size fixed for these buffers must evenly divide into the max value of an unsigned 16-bit integer (65536). The data structure is described in [this blog post by Glenn Fiedler](https://gafferongames.com/post/reliable_ordered_messages/).

We keep track of a counter (oui), representing the last consecutive sequence number of a packet we have sent that was acknowledged by our peer. For example, if we have sent packets whose sequence numbers are in the range [0, 256], and we have received acknowledgements for packets (0, 1, 2, 3, 4, 8, 9, 10, 11, 12), then (oui) would be 4.

Let cap(q) be the fixed size or capacity of sequence buffer q.

While sending packets, we intermittently stop and buffer the sending of packets if we believe sending more packets would overflow the read buffer of our recipient. More explicitly, if the next packet we sent is assigned a packet number greater than (oui + cap(rq)), we stop all sends until (oui) has incremented through the recipient of a packet from our peer.

### Retransmitting Lost Packets

The logic for retransmitting stale, unacknowledged sent packets and maintaining acknowledgements was taken with credit to [this blog post by Glenn Fiedler](https://gafferongames.com/post/reliable_ordered_messages/).

Packets are suspected to be lost if they are not acknowledged by their recipient after 100ms. Once a packet is suspected to be lost, it is resent. As of right now, packets are resent for a maximum of 10 times.

It might be wise to not allow packets to be resent a capped number of times, and to leave it up to the developer. However, that is open for discussion which I am happy to have over on my Discord server or through a Github issue.

## Rationale

On my quest for finding a feasible solution against TCP head-of-line blocking, I looked through a _lot_ of reliable UDP libraries in Go, with the majority primarily suited for either file transfer or gaming:

1. A direct port of the reference C implementation of [networkprotocol/reliable.io](https://github.com/networkprotocol/reliable.io): [jakecoffman/rely](https://github.com/jakecoffman/rely)
2. A realtime multiplayer network gaming protocol: [obsilp/rmnp](https://github.com/obsilp/rmnp)
3. A reliable, production-grade ARQ protocol: [xtaci/kcp-go](https://github.com/xtaci/kcp-go)
4. An encrypted session-based streaming protocol: [ooclab/es](https://github.com/ooclab/es/tree/master/proto/udp)
5. A game networking protocol: [arl/udpnet](https://github.com/arl/udpnet)
6. A direct port of uTP (Micro Transport Protocol): [warjiang/utp](https://github.com/warjiang/utp/tree/master/utp)
7. A protocol for sending arbitrarily large, chunked amounts of data: [go-guoyk/sptp](https://github.com/go-guoyk/sptp)
8. A small-scale fast transmission protocol: [spance/suft](https://github.com/spance/suft/)
9. A direct port of QUIC: [lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go)

Going through all of them, I felt that they did just a little too much for me. For my work and side projects, I have been working heavily on decentralized p2p networking protocols. The nature of these protocols is that they suffer heavily from TCP head-of-line blocking operating in high-latency/high packet loss environments.

In many cases, a lot of the features provided by these libraries were either not needed, or honestly felt like they would best be handled and thought through by the developer using these libraries. For example:
 
1. handshaking/session management
2. packet fragmentation/reassembly
3. packet encryption/decryption

So, I began working on a modular approach and decided to abstract away the reliability portion of protocols I have built into a separate library.

I feel that this approach is best versus the popular alternatives like QUIC or SCTP that may, depending on your circumstances, do just a bit too much for you. After all, getting _just_ the reliability bits of a UDP-based protocol correct and well-tested is hard enough.

## Todo

1. Estimate the round-trip time (RTT) and adjust the system's packet re-transmission delay based off of it.
2. Encapsulate away protocol logic and `net.PacketConn`-related bits for a finer abstraction.
3. Keep a cache of the string representations of passed-in `net.UDPAddr`.
4. Reduce locking in as many code hot paths as possible.
5. Networking statistics (packet loss, RTT, etc.).
6. More unit tests.

## Usage

**reliable** uses Go modules. To include it in your project, run the following command:

```
$ go get github.com/lithdew/reliable
```

Should you just be looking to quickly get a project or demo up and running, use `Endpoint`. If you require more flexibility, consider directly working with `Conn`.

Note that some sort of keep-alive mechanism or heartbeat system needs to be bootstrapped on top, otherwise packets may indefinitely be resent as they will have failed to be acknowledged. 

## Options

1. The read buffer size may be configured using `WithReadBufferSize`. The default read buffer size is 256.
2. The write buffer size may be configured using `WithWriteBufferSize`. The default write buffer size is 256.
3. The minimum period of time before we retransmit an packet that has yet to be acknowledged may be configured using `WithResendTimeout`. The default resend timeout is 100 milliseconds.
4. A packet handler which is to be called back when a packet is received may be configured using `WithEndpointPacketHandler` or `WithProtocolPacketHandler`. By default, a nil handler is provided which ignores all incoming packets.
5. An error handler which is called when errors occur on a connection that may be configured using `WithEndpointErrorHandler` or `WithProtocolErrorHandler`. By default, a nil handler is provided which ignores all errors.
6. A byte buffer pool may be passed in using `WithBufferPool`. By default, a new byte buffer pool is instantiated.

## Benchmarks

A benchmark was done using [`cmd/benchmark`](examples/benchmark) from Japan to a DigitalOcean 2GB / 60 GB Disk / NYC3 server.

The benchmark task was to spam 1400 byte packets from Japan to New York. Given a ping latency of roughly 220 milliseconds, the throughput was roughly 1.2 MiB/sec.

Unit test benchmarks have also been performed, as shown below.

```
$ cat /proc/cpuinfo | grep 'model name' | uniq
model name : Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz

$ go test -bench=. -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/lithdew/reliable
BenchmarkEndpointWriteReliablePacket-8           2053717              5941 ns/op             183 B/op          9 allocs/op
BenchmarkEndpointWriteUnreliablePacket-8         2472392              4866 ns/op             176 B/op          8 allocs/op
BenchmarkMarshalPacketHeader-8                  749060137               15.7 ns/op             0 B/op          0 allocs/op
BenchmarkUnmarshalPacketHeader-8                835547473               14.6 ns/op             0 B/op          0 allocs/op
```

## Example

You may run the example below by executing the following command:

```
$ go run github.com/lithdew/reliable/examples/basic
```

This example demonstrates:

1. how to quickly construct two UDP endpoints listening on ports 44444 and 55555, and
2. how to have the UDP endpoint at port 44444 spam 1400-byte packets to the UDP endpoint at port 55555 as fast as possible.

```go
package main

import (
	"bytes"
	"errors"
	"github.com/davecgh/go-spew/spew"
	"github.com/lithdew/reliable"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"time"
)

var (
	PacketData = bytes.Repeat([]byte("x"), 1400)
	NumPackets = uint64(0)
)

func check(err error) {
	if err != nil && !errors.Is(err, io.EOF) {
		log.Panic(err)
	}
}

func listen(addr string) net.PacketConn {
	conn, err := net.ListenPacket("udp", addr)
	check(err)
	return conn
}

func handler(buf []byte, _ net.Addr) {
	if bytes.Equal(buf, PacketData) || len(buf) == 0 {
		return
	}
	spew.Dump(buf)
	os.Exit(1)
}

func main() {
	exit := make(chan struct{})

	var wg sync.WaitGroup
	wg.Add(2)

	ca := listen("127.0.0.1:44444")
	cb := listen("127.0.0.1:55555")

	a := reliable.NewEndpoint(ca, reliable.WithEndpointPacketHandler(handler))
	b := reliable.NewEndpoint(cb, reliable.WithEndpointPacketHandler(handler))

	defer func() {
		check(ca.SetDeadline(time.Now().Add(1 * time.Millisecond)))
		check(cb.SetDeadline(time.Now().Add(1 * time.Millisecond)))

		close(exit)

		check(a.Close())
		check(b.Close())

		check(ca.Close())
		check(cb.Close())

		wg.Wait()
	}()

	go a.Listen()
	go b.Listen()

	// The two goroutines below have endpoint A spam endpoint B, and print out how
	// many packets of data are being sent per second.

	go func() {
		defer wg.Done()

		for {
			select {
			case <-exit:
				return
			default:
			}

			check(a.WriteReliablePacket(PacketData, b.Addr()))
			atomic.AddUint64(&NumPackets, 1)
		}
	}()

	go func() {
		defer wg.Done()

		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop()

		for {
			select {
			case <-exit:
				return
			case <-ticker.C:
				numPackets := atomic.SwapUint64(&NumPackets, 0)
				numBytes := float64(numPackets) * 1400.0 / 1024.0 / 1024.0

				log.Printf(
					"Sent %d packet(s) comprised of %.2f MiB worth of data.",
					numPackets,
					numBytes,
				)
			}
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	<-ch
}
```

================================================
FILE: conn.go
================================================
package reliable

import (
	"fmt"
	"io"
	"net"
)

type transmitFunc func(buf []byte) (bool, error)

type Conn struct {
	addr     net.Addr
	conn     net.PacketConn
	protocol *Protocol
}

func NewConn(addr net.Addr, conn net.PacketConn, opts ...ProtocolOption) *Conn {
	p := NewProtocol(opts...)
	return &Conn{addr: addr, conn: conn, protocol: p}
}

func (c *Conn) WriteReliablePacket(buf []byte) error {
	buf, err := c.protocol.WritePacket(true, buf)
	if err != nil {
		return err
	}

	_, err = c.transmit(buf)
	return err
}

func (c *Conn) WriteUnreliablePacket(buf []byte) error {
	buf, err := c.protocol.WritePacket(false, buf)
	if err != nil {
		return err
	}

	_, err = c.transmit(buf)
	return err
}

func (c *Conn) Read(header PacketHeader, buf []byte) error {
	buf = c.protocol.ReadPacket(header, buf)

	if len(buf) != 0 {
		_, err := c.transmit(buf)
		return err
	}

	return nil
}

func (c *Conn) Close() {
	c.protocol.Close()
}

func (c *Conn) Run() {
	c.protocol.Run(c.transmit)
}

func (c *Conn) transmit(buf []byte) (EOF bool, err error) {
	n, err := c.conn.WriteTo(buf, c.addr)

	if err == nil && n != len(buf) {
		err = io.ErrShortWrite
	}

	EOF = isEOF(err)

	if err != nil && !EOF {
		err = fmt.Errorf("failed to transmit packet: %w", err)
		return
	}

	return
}


================================================
FILE: conn_test.go
================================================
package reliable

import (
	"bytes"
	"github.com/stretchr/testify/require"
	"go.uber.org/goleak"
	"math"
	"net"
	"sync/atomic"
	"testing"
	"time"
)

func TestConnWriteReliablePacket(t *testing.T) {
	defer goleak.VerifyNone(t)

	data := bytes.Repeat([]byte("x"), 1400)

	actual := uint64(0)
	expected := uint64(65536)

	a, _ := net.ListenPacket("udp", "127.0.0.1:0")
	b, _ := net.ListenPacket("udp", "127.0.0.1:0")

	handler := func(buf []byte, _ uint16) {
		atomic.AddUint64(&actual, 1)
		require.EqualValues(t, data, buf)
	}

	ca := NewConn(a.LocalAddr(), a, WithProtocolPacketHandler(handler))
	cb := NewConn(b.LocalAddr(), b, WithProtocolPacketHandler(handler))

	go readLoop(t, a, ca)
	go readLoop(t, b, cb)

	defer func() {
		require.NoError(t, a.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, b.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(t, a.Close())
		require.NoError(t, b.Close())

		ca.Close()
		cb.Close()

		require.EqualValues(t, expected, atomic.LoadUint64(&actual))
	}()

	for i := uint64(0); i < expected; i++ {
		require.NoError(t, ca.WriteReliablePacket(data))
	}
}

func readLoop(t *testing.T, pc net.PacketConn, c *Conn) {
	var (
		n   int
		err error
	)

	buf := make([]byte, math.MaxUint16+1)
	for {
		n, _, err = pc.ReadFrom(buf)
		if err != nil {
			break
		}

		header, buf, err := UnmarshalPacketHeader(buf[:n])
		require.NoError(t, err)

		if err == nil {
			err = c.Read(header, buf)
			require.NoError(t, err)
		}
	}
}


================================================
FILE: endpoint.go
================================================
package reliable

import (
	"io"
	"math"
	"net"
	"sync"
	"sync/atomic"
	"time"
)

type EndpointPacketHandler func(buf []byte, addr net.Addr)
type EndpointErrorHandler func(err error, addr net.Addr)

type Endpoint struct {
	writeBufferSize uint16 // write buffer size that must be a divisor of 65536
	readBufferSize  uint16 // read buffer size that must be a divisor of 65536

	updatePeriod  time.Duration // how often time-dependant parts of the protocol get checked
	resendTimeout time.Duration // how long we wait until unacked packets should be resent

	mu sync.Mutex
	wg sync.WaitGroup

	pool *Pool

	ph EndpointPacketHandler
	eh EndpointErrorHandler

	addr  net.Addr
	conn  net.PacketConn
	conns map[string]*Conn

	closing uint32
}

func NewEndpoint(conn net.PacketConn, opts ...EndpointOption) *Endpoint {
	e := &Endpoint{conn: conn, addr: conn.LocalAddr(), conns: make(map[string]*Conn)}

	for _, opt := range opts {
		opt.applyEndpoint(e)
	}

	if e.writeBufferSize == 0 {
		e.writeBufferSize = DefaultWriteBufferSize
	}

	if e.readBufferSize == 0 {
		e.readBufferSize = DefaultReadBufferSize
	}

	if e.resendTimeout == 0 {
		e.resendTimeout = DefaultResendTimeout
	}

	if e.updatePeriod == 0 {
		e.updatePeriod = DefaultUpdatePeriod
	}

	if e.pool == nil {
		e.pool = new(Pool)
	}

	return e
}

func (e *Endpoint) getConn(addr net.Addr) *Conn {
	id := addr.String()

	e.mu.Lock()
	defer e.mu.Unlock()

	conn := e.conns[id]
	if conn == nil {
		if atomic.LoadUint32(&e.closing) == 1 {
			return nil
		}

		conn = NewConn(
			addr,
			e.conn,
			WithWriteBufferSize(e.writeBufferSize),
			WithReadBufferSize(e.readBufferSize),
			WithUpdatePeriod(e.updatePeriod),
			WithResendTimeout(e.resendTimeout),
			WithBufferPool(e.pool),
		)

		e.wg.Add(1)
		go func() {
			defer e.wg.Done()
			conn.Run()
		}()

		e.conns[id] = conn
	}

	return conn
}

func (e *Endpoint) clearConn(addr net.Addr) {
	id := addr.String()

	e.mu.Lock()
	conn := e.conns[id]
	delete(e.conns, id)
	e.mu.Unlock()

	conn.Close()
}

func (e *Endpoint) clearConns() {
	e.mu.Lock()
	conns := make([]*Conn, 0, len(e.conns))
	for id, conn := range e.conns {
		conns = append(conns, conn)
		delete(e.conns, id)
	}
	e.mu.Unlock()

	for _, conn := range conns {
		conn.Close()
	}
}

func (e *Endpoint) Addr() net.Addr {
	return e.addr
}

func (e *Endpoint) WriteReliablePacket(buf []byte, addr net.Addr) error {
	conn := e.getConn(addr)
	if conn == nil {
		return io.EOF
	}
	return conn.WriteReliablePacket(buf)
}

func (e *Endpoint) WriteUnreliablePacket(buf []byte, addr net.Addr) error {
	conn := e.getConn(addr)
	if conn == nil {
		return io.EOF
	}
	return conn.WriteUnreliablePacket(buf)
}

func (e *Endpoint) Listen() {
	e.mu.Lock()
	e.wg.Add(1)
	e.mu.Unlock()

	defer e.wg.Done()

	var (
		n    int
		addr net.Addr
		err  error
	)

	buf := make([]byte, math.MaxUint16+1)
	for {
		n, addr, err = e.conn.ReadFrom(buf)
		if err != nil {
			break
		}

		conn := e.getConn(addr)
		if conn == nil {
			break
		}

		header, buf, err := UnmarshalPacketHeader(buf[:n])
		if err != nil {
			e.clearConn(addr)

			if e.eh != nil {
				e.eh(err, e.addr)
			}

			continue
		}

		err = conn.Read(header, buf)
		if err != nil {
			e.clearConn(addr)

			if e.eh != nil {
				e.eh(err, e.addr)
			}

			continue
		}

		if e.ph != nil {
			e.ph(buf, e.addr)
		}
	}

	e.clearConns()
}

func (e *Endpoint) Close() error {
	atomic.StoreUint32(&e.closing, 1)
	e.wg.Wait()
	return nil
}


================================================
FILE: endpoint_test.go
================================================
package reliable

import (
	"bytes"
	"github.com/stretchr/testify/require"
	"go.uber.org/goleak"
	"net"
	"sort"
	"strconv"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

func newPacketConn(t testing.TB, addr string) net.PacketConn {
	t.Helper()
	conn, err := net.ListenPacket("udp", addr)
	require.NoError(t, err)
	return conn
}

func BenchmarkEndpointWriteReliablePacket(b *testing.B) {
	ca := newPacketConn(b, "127.0.0.1:0")
	cb := newPacketConn(b, "127.0.0.1:0")

	ea := NewEndpoint(ca)
	eb := NewEndpoint(cb)

	go ea.Listen()
	go eb.Listen()

	defer func() {
		require.NoError(b, ca.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(b, cb.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(b, ea.Close())
		require.NoError(b, eb.Close())

		require.NoError(b, ca.Close())
		require.NoError(b, cb.Close())
	}()

	data := bytes.Repeat([]byte("x"), 1400)

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		if err := ea.WriteReliablePacket(data, eb.Addr()); err != nil && !isEOF(err) {
			b.Fatal(err)
		}
	}
}

func BenchmarkEndpointWriteUnreliablePacket(b *testing.B) {
	ca := newPacketConn(b, "127.0.0.1:0")
	cb := newPacketConn(b, "127.0.0.1:0")

	ea := NewEndpoint(ca)
	eb := NewEndpoint(cb)

	go ea.Listen()
	go eb.Listen()

	defer func() {
		require.NoError(b, ca.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(b, cb.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(b, ea.Close())
		require.NoError(b, eb.Close())

		require.NoError(b, ca.Close())
		require.NoError(b, cb.Close())
	}()

	data := bytes.Repeat([]byte("x"), 1400)

	b.ReportAllocs()
	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		if err := ea.WriteUnreliablePacket(data, eb.Addr()); err != nil && !isEOF(err) {
			b.Fatal(err)
		}
	}
}

func TestEndpointWriteReliablePacket(t *testing.T) {
	defer goleak.VerifyNone(t)

	var mu sync.Mutex

	values := make(map[string]struct{})

	actual := uint64(0)
	expected := uint64(65536)

	handler := func(buf []byte, _ net.Addr) {
		if len(buf) == 0 {
			return
		}

		atomic.AddUint64(&actual, 1)

		mu.Lock()
		_, exists := values[string(buf)]
		delete(values, string(buf))
		mu.Unlock()

		require.True(t, exists)
	}

	ca := newPacketConn(t, "127.0.0.1:0")
	cb := newPacketConn(t, "127.0.0.1:0")

	a := NewEndpoint(ca, WithEndpointPacketHandler(handler))
	b := NewEndpoint(cb, WithEndpointPacketHandler(handler))

	go a.Listen()
	go b.Listen()

	defer func() {
		require.NoError(t, ca.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, cb.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(t, a.Close())
		require.NoError(t, b.Close())

		require.NoError(t, ca.Close())
		require.NoError(t, cb.Close())

		require.EqualValues(t, expected, atomic.LoadUint64(&actual))
	}()

	for i := uint64(0); i < expected; i++ {
		data := strconv.AppendUint(nil, i, 10)

		mu.Lock()
		values[string(data)] = struct{}{}
		mu.Unlock()

		require.NoError(t, a.WriteReliablePacket(data, b.Addr()))
	}
}

func TestEndpointWriteReliablePacketEndToEnd(t *testing.T) {
	defer goleak.VerifyNone(t)

	actual := uint64(0)
	expected := uint64(512)

	handler := func(buf []byte, _ net.Addr) {
		if len(buf) == 0 {
			return
		}
		atomic.AddUint64(&actual, 1)
	}

	ca := newPacketConn(t, "127.0.0.1:0")
	cb := newPacketConn(t, "127.0.0.1:0")

	a := NewEndpoint(ca, WithEndpointPacketHandler(handler))
	b := NewEndpoint(cb, WithEndpointPacketHandler(handler))

	go a.Listen()
	go b.Listen()

	defer func() {
		require.NoError(t, ca.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, cb.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(t, a.Close())
		require.NoError(t, b.Close())

		require.NoError(t, ca.Close())
		require.NoError(t, cb.Close())

		require.EqualValues(t, expected*2, atomic.LoadUint64(&actual))
	}()

	for i := uint64(0); i < expected; i++ {
		data := strconv.AppendUint(nil, i, 10)

		require.NoError(t, a.WriteReliablePacket(data, b.Addr()))
		require.NoError(t, b.WriteReliablePacket(data, a.Addr()))
	}
}

// Check whether race condition happen
// Simulate write and read heavy condition by sending packet concurrently
func TestRaceConditions(t *testing.T) {
	defer goleak.VerifyNone(t)

	var expected int = 1000
	tr := newTestRaceConditions(expected)

	handler := func(buf []byte, _ net.Addr) {
		if len(buf) == 0 {
			return
		}
		tr.append(buf)
	}

	ca := newPacketConn(t, "127.0.0.1:0")
	cb := newPacketConn(t, "127.0.0.1:0")
	cc := newPacketConn(t, "127.0.0.1:0")
	cd := newPacketConn(t, "127.0.0.1:0")
	ce := newPacketConn(t, "127.0.0.1:0")

	a := NewEndpoint(ca, WithEndpointPacketHandler(handler))
	b := NewEndpoint(cb, WithEndpointPacketHandler(handler))
	c := NewEndpoint(cc, WithEndpointPacketHandler(handler))
	d := NewEndpoint(cd, WithEndpointPacketHandler(handler))
	e := NewEndpoint(ce, WithEndpointPacketHandler(handler))

	go a.Listen()
	go b.Listen()
	go c.Listen()
	go d.Listen()
	go e.Listen()

	defer func() {
		tr.wait()

		// Note: Guarantee that all messages are deliverd
		time.Sleep(100 * time.Millisecond)

		require.NoError(t, ca.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, cb.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, cc.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, cd.SetDeadline(time.Now().Add(1*time.Millisecond)))
		require.NoError(t, ce.SetDeadline(time.Now().Add(1*time.Millisecond)))

		require.NoError(t, a.Close())
		require.NoError(t, b.Close())
		require.NoError(t, c.Close())
		require.NoError(t, d.Close())
		require.NoError(t, e.Close())

		require.NoError(t, ca.Close())
		require.NoError(t, cb.Close())
		require.NoError(t, cc.Close())
		require.NoError(t, cd.Close())
		require.NoError(t, ce.Close())

		require.EqualValues(t, tr.expected, uniqSort(tr.actual))
	}()

	tr.wg.Add(1)
	sB := tr.expected[0 : len(tr.expected)/4]
	go func() {
		defer tr.done()
		for i := 0; i < len(sB); i++ {
			data := []byte(strconv.Itoa(sB[i]))

			err := a.WriteReliablePacket(data, b.Addr())
			if err != nil {
				require.True(t, isEOF(err))
			}
		}
	}()

	tr.wg.Add(1)
	sC := tr.expected[len(tr.expected)/4 : len(tr.expected)*2/4]
	go func() {
		defer tr.done()
		for i := 0; i < len(sC); i++ {
			data := []byte(strconv.Itoa(sC[i]))

			err := a.WriteReliablePacket(data, c.Addr())
			if err != nil {
				require.True(t, isEOF(err))
			}
		}
	}()

	tr.wg.Add(1)
	sD := tr.expected[len(tr.expected)*2/4 : len(tr.expected)*3/4]
	go func() {
		defer tr.done()
		for i := 0; i < len(sD); i++ {
			data := []byte(strconv.Itoa(sD[i]))

			err := a.WriteReliablePacket(data, d.Addr())
			if err != nil {
				require.True(t, isEOF(err))
			}
		}
	}()

	tr.wg.Add(1)
	sE := tr.expected[len(tr.expected)*3/4:]
	go func() {
		defer tr.done()
		for i := 0; i < len(sE); i++ {
			data := []byte(strconv.Itoa(sE[i]))

			err := a.WriteReliablePacket(data, e.Addr())
			if err != nil {
				require.True(t, isEOF(err))
			}
		}
	}()
}

// Note: This struct is test for TestRaceConditions
// The purpose for this struct is to prevent race condition of WaitGroup
type testRaceConditions struct {
	mu       sync.Mutex
	wg       sync.WaitGroup
	expected []int
	actual   []int
}

func newTestRaceConditions(cap int) *testRaceConditions {
	return &testRaceConditions{expected: genNumSlice(cap)}
}

func (t *testRaceConditions) done() {
	t.mu.Lock()
	defer t.mu.Unlock()
	t.wg.Done()
}

func (t *testRaceConditions) wait() {
	t.wg.Wait()
}

func (t *testRaceConditions) append(buf []byte) {
	t.mu.Lock()
	defer t.mu.Unlock()

	num, _ := strconv.Atoi(string(buf))
	t.actual = append(t.actual, num)
}

func genNumSlice(len int) (s []int) {
	for i := 0; i < len; i++ {
		s = append(s, i)
	}
	return
}

func uniqSort(s []int) (result []int) {
	sort.Ints(s)
	var pre int
	for i := 0; i < len(s); i++ {
		if i == 0 || s[i] != pre {
			result = append(result, s[i])
		}
		pre = s[i]
	}
	return
}


================================================
FILE: error.go
================================================
package reliable

import (
	"errors"
	"io"
	"net"
)

func isEOF(err error) bool {
	if errors.Is(err, io.EOF) {
		return true
	}

	var netErr *net.OpError
	if errors.As(err, &netErr) {
		if netErr.Err.Error() == "use of closed network connection" {
			return true
		}
		if netErr.Timeout() {
			return true
		}
	}

	return false
}


================================================
FILE: examples/basic/main.go
================================================
package main

import (
	"bytes"
	"errors"
	"github.com/davecgh/go-spew/spew"
	"github.com/lithdew/reliable"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"time"
)

var (
	PacketData = bytes.Repeat([]byte("x"), 1400)
	NumPackets = uint64(0)
)

func check(err error) {
	if err != nil && !errors.Is(err, io.EOF) {
		log.Panic(err)
	}
}

func listen(addr string) net.PacketConn {
	conn, err := net.ListenPacket("udp", addr)
	check(err)
	return conn
}

func handler(buf []byte, _ net.Addr) {
	if bytes.Equal(buf, PacketData) || len(buf) == 0 {
		return
	}
	spew.Dump(buf)
	os.Exit(1)
}

func main() {
	exit := make(chan struct{})

	var wg sync.WaitGroup
	wg.Add(2)

	ca := listen("127.0.0.1:44444")
	cb := listen("127.0.0.1:55555")

	a := reliable.NewEndpoint(ca, reliable.WithEndpointPacketHandler(handler))
	b := reliable.NewEndpoint(cb, reliable.WithEndpointPacketHandler(handler))

	defer func() {
		check(ca.SetDeadline(time.Now().Add(1 * time.Millisecond)))
		check(cb.SetDeadline(time.Now().Add(1 * time.Millisecond)))

		close(exit)

		check(a.Close())
		check(b.Close())

		check(ca.Close())
		check(cb.Close())

		wg.Wait()
	}()

	go a.Listen()
	go b.Listen()

	// The two goroutines below have endpoint A spam endpoint B, and print out how
	// many packets of data are being sent per second.

	go func() {
		defer wg.Done()

		for {
			select {
			case <-exit:
				return
			default:
			}

			check(a.WriteReliablePacket(PacketData, b.Addr()))
			atomic.AddUint64(&NumPackets, 1)
		}
	}()

	go func() {
		defer wg.Done()

		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop()

		for {
			select {
			case <-exit:
				return
			case <-ticker.C:
				numPackets := atomic.SwapUint64(&NumPackets, 0)
				numBytes := float64(numPackets) * 1400.0 / 1024.0 / 1024.0

				log.Printf(
					"Sent %d packet(s) comprised of %.2f MiB worth of data.",
					numPackets,
					numBytes,
				)
			}
		}
	}()

	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	<-ch
}


================================================
FILE: examples/benchmark/main.go
================================================
package main

import (
	"bytes"
	"errors"
	"flag"
	"github.com/lithdew/reliable"
	"io"
	"log"
	"net"
	"sync/atomic"
	"time"
)

var (
	listener bool
)

func check(err error) {
	if err != nil && !errors.Is(err, io.EOF) {
		log.Panic(err)
	}
}

func listen(addr string) net.PacketConn {
	conn, err := net.ListenPacket("udp", addr)
	check(err)

	log.Printf("%s: Listening for peers.", conn.LocalAddr())

	return conn
}

func main() {
	flag.BoolVar(&listener, "l", false, "either listen or dial")
	flag.Parse()

	host := flag.Arg(0)
	if !listener || host == "" {
		host = ":0"
	}

	conn := listen(host)

	counter := uint64(0)

	handler := func(buf []byte, addr net.Addr) {
		if len(buf) == 0 {
			return
		}
		//log.Printf("%s->%s: (seq=%d) (size=%d)", addr.String(), conn.LocalAddr().String(), seq, len(buf))
		atomic.AddUint64(&counter, 1)
	}

	endpoint := reliable.NewEndpoint(conn, reliable.WithEndpointPacketHandler(handler))
	go endpoint.Listen()

	defer func() {
		check(endpoint.Close())
		check(conn.Close())
	}()

	if listener {
		for range time.Tick(1 * time.Second) {
			numPackets := atomic.SwapUint64(&counter, 0)
			numBytes := float64(numPackets) * 1400.0 / 1024.0 / 1024.0

			log.Printf("%s: Received %d packets (%.2f MiB).", conn.LocalAddr(), numPackets, numBytes)
		}
	}

	addr, err := net.ResolveUDPAddr("udp", flag.Arg(0))
	check(err)

	data := bytes.Repeat([]byte("x"), 1400)

	go func() {
		for range time.Tick(1 * time.Second) {
			numPackets := atomic.SwapUint64(&counter, 0)
			numBytes := float64(numPackets) * 1400.0 / 1024.0 / 1024.0

			log.Printf("%s: Sent %d packets (%.2f MiB).", conn.LocalAddr(), numPackets, numBytes)
		}
	}()

	for {
		check(endpoint.WriteReliablePacket(data, addr))
		atomic.AddUint64(&counter, 1)
	}
}


================================================
FILE: fuzz.go
================================================
// +build gofuzz

package reliable

import (
	"bytes"
	"errors"
	"net"
	"time"
)

func Fuzz(data []byte) int {
	ca, err := net.ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		return -1
	}
	cb, err := net.ListenPacket("udp", "127.0.0.1:0")
	if err != nil {
		return -1
	}

	chErr := make(chan error)

	handler := func(buf []byte, _ net.Addr) {
		if len(buf) == 0 || bytes.Equal(buf, data) {
			return
		}
		chErr <- errors.New("data miss match")
	}

	ea := NewEndpoint(ca, reliable.WithEndpointPacketHandler(handler))
	eb := NewEndpoint(cb, reliable.WithEndpointPacketHandler(handler))

	go ea.Listen()
	go eb.Listen()

	for i := 0; i < 65536; i++ {
		select {
		case <-chErr:
			return 0
		default:
			if err := ea.WriteReliablePacket(data, eb.Addr()); err != nil && !isEOF(err) {
				return 0
			}
		}
	}

	if err := ca.SetDeadline(time.Now().Add(1 * time.Millisecond)); err != nil {
		return 0
	}

	if err := cb.SetDeadline(time.Now().Add(1 * time.Millisecond)); err != nil {
		return 0
	}

	if err := ea.Close(); err != nil {
		return 0
	}
	if err := eb.Close(); err != nil {
		return 0
	}

	if err := ca.Close(); err != nil {
		return 0
	}
	if err := cb.Close(); err != nil {
		return 0
	}

	return 1
}


================================================
FILE: go.mod
================================================
module github.com/lithdew/reliable

go 1.14

require (
	github.com/davecgh/go-spew v1.1.1
	github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 // indirect
	github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59
	github.com/lithdew/seq v0.0.0-20200504083424-74d5d8117a05
	github.com/stretchr/testify v1.5.1
	github.com/valyala/bytebufferpool v1.0.0
	go.uber.org/goleak v1.0.0
	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
	golang.org/x/tools v0.0.0-20200501005904-d351ea090f9b // indirect
)


================================================
FILE: go.sum
================================================
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59 h1:CQpoOQecHxhvgOU/ijue/yWuShZYDtNpI9bsD4Dkzrk=
github.com/lithdew/bytesutil v0.0.0-20200409052507-d98389230a59/go.mod h1:89JlULMIJ/+YWzAp5aHXgAD2d02S2mY+a+PMgXDtoNs=
github.com/lithdew/seq v0.0.0-20200504083424-74d5d8117a05 h1:j1UtG8NYCupA5xUwQ/vrTf/zjuNlZ0D1n7UtM8LhS58=
github.com/lithdew/seq v0.0.0-20200504083424-74d5d8117a05/go.mod h1:4vVgbfmYc+ZIh0dy99HRrM6knnAtQXNI8MOx+1pUYso=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo=
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11 h1:Yq9t9jnGoR+dBuitxdo9l6Q7xh/zOyNnYUtDKaQ3x0E=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200501005904-d351ea090f9b h1:2hSR2MyOaYEy6yJYg/CpErymr/m7xJEJpm9kfT7ZMg4=
golang.org/x/tools v0.0.0-20200501005904-d351ea090f9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=


================================================
FILE: options.go
================================================
package reliable

import "time"

const (
	DefaultWriteBufferSize uint16 = 256
	DefaultReadBufferSize  uint16 = 256

	DefaultUpdatePeriod  = 100 * time.Millisecond
	DefaultResendTimeout = 100 * time.Millisecond
)

type ProtocolOption interface {
	applyProtocol(p *Protocol)
}

type EndpointOption interface {
	applyEndpoint(e *Endpoint)
}

type Option interface {
	ProtocolOption
	EndpointOption
}

type withBufferPool struct{ pool *Pool }

func (o withBufferPool) applyProtocol(p *Protocol) { p.pool = o.pool }
func (o withBufferPool) applyEndpoint(e *Endpoint) { e.pool = o.pool }

func WithBufferPool(pool *Pool) Option { return withBufferPool{pool: pool} }

type withWriteBufferSize struct{ writeBufferSize uint16 }

func (o withWriteBufferSize) applyProtocol(p *Protocol) { p.writeBufferSize = o.writeBufferSize }
func (o withWriteBufferSize) applyEndpoint(e *Endpoint) { e.writeBufferSize = o.writeBufferSize }

func WithWriteBufferSize(writeBufferSize uint16) Option {
	if 65536%uint32(writeBufferSize) != 0 {
		panic("write buffer size must be smaller than 65536 and a power of two")
	}
	return withWriteBufferSize{writeBufferSize: writeBufferSize}
}

type withReadBufferSize struct{ readBufferSize uint16 }

func (o withReadBufferSize) applyProtocol(p *Protocol) { p.readBufferSize = o.readBufferSize }
func (o withReadBufferSize) applyEndpoint(e *Endpoint) { e.readBufferSize = o.readBufferSize }

func WithReadBufferSize(readBufferSize uint16) Option {
	if 65536%uint32(readBufferSize) != 0 {
		panic("read buffer size must be smaller than 65536 and a power of two")
	}
	return withReadBufferSize{readBufferSize: readBufferSize}
}

type withProtocolPacketHandler struct{ ph ProtocolPacketHandler }
type withEndpointPacketHandler struct{ ph EndpointPacketHandler }

func (o withProtocolPacketHandler) applyProtocol(p *Protocol) { p.ph = o.ph }
func (o withEndpointPacketHandler) applyEndpoint(e *Endpoint) { e.ph = o.ph }

func WithProtocolPacketHandler(ph ProtocolPacketHandler) ProtocolOption {
	return withProtocolPacketHandler{ph: ph}
}
func WithEndpointPacketHandler(ph EndpointPacketHandler) EndpointOption {
	return withEndpointPacketHandler{ph: ph}
}

type withProtocolErrorHandler struct{ eh ProtocolErrorHandler }
type withEndpointErrorHandler struct{ eh EndpointErrorHandler }

func (o withProtocolErrorHandler) applyProtocol(p *Protocol) { p.eh = o.eh }
func (o withEndpointErrorHandler) applyEndpoint(e *Endpoint) { e.eh = o.eh }

func WithProtocolErrorHandler(eh ProtocolErrorHandler) ProtocolOption {
	return withProtocolErrorHandler{eh: eh}
}
func WithEndpointErrorHandler(eh EndpointErrorHandler) EndpointOption {
	return withEndpointErrorHandler{eh: eh}
}

type withUpdatePeriod struct{ updatePeriod time.Duration }

func (o withUpdatePeriod) applyProtocol(p *Protocol) { p.updatePeriod = o.updatePeriod }
func (o withUpdatePeriod) applyEndpoint(e *Endpoint) { e.updatePeriod = o.updatePeriod }

func WithUpdatePeriod(updatePeriod time.Duration) Option {
	if updatePeriod == 0 {
		panic("update period of zero is not supported yet")
	}
	return withUpdatePeriod{updatePeriod: updatePeriod}
}

type withResendTimeout struct{ resendTimeout time.Duration }

func (o withResendTimeout) applyProtocol(p *Protocol) { p.resendTimeout = o.resendTimeout }
func (o withResendTimeout) applyEndpoint(e *Endpoint) { e.resendTimeout = o.resendTimeout }

func WithResendTimeout(resendTimeout time.Duration) Option {
	if resendTimeout == 0 {
		panic("ack timeout of zero is not supported yet")
	}
	return withResendTimeout{resendTimeout: resendTimeout}
}


================================================
FILE: packet.go
================================================
package reliable

import (
	"github.com/lithdew/bytesutil"
	"github.com/valyala/bytebufferpool"
	"io"
	"math/bits"
	"time"
)

const ACKBitsetSize = 32

type (
	Buffer = bytebufferpool.ByteBuffer
	Pool   = bytebufferpool.Pool
)

type writtenPacket struct {
	buf     *Buffer   // pooled contents of this packet
	acked   bool      // whether or not this packet was acked
	written time.Time // last time the packet was written
	resent  byte      // total number of times this packet was resent
}

func (p writtenPacket) shouldResend(now time.Time, resendTimeout time.Duration) bool {
	return !p.acked && p.resent < 10 && now.Sub(p.written) >= resendTimeout
}

type PacketHeaderFlag uint8

const (
	FlagFragment PacketHeaderFlag = 1 << iota
	FlagA
	FlagB
	FlagC
	FlagD
	FlagACKEncoded
	FlagEmpty
	FlagUnordered
)

func (p PacketHeaderFlag) Toggle(flag PacketHeaderFlag) PacketHeaderFlag {
	return p | flag
}

func (p PacketHeaderFlag) Toggled(flag PacketHeaderFlag) bool {
	return p&flag != 0
}

func (p PacketHeaderFlag) AppendTo(dst []byte) []byte {
	return append(dst, byte(p))
}

type PacketHeader struct {
	Sequence  uint16
	ACK       uint16
	ACKBits   uint32
	Unordered bool
	Empty     bool
}

func (p PacketHeader) AppendTo(dst []byte) []byte {
	// Mark a flag byte to RLE-encode the ACK bitset.

	flag := PacketHeaderFlag(0)
	if p.ACKBits&0x000000FF != 0x000000FF {
		flag = flag.Toggle(FlagA)
	}
	if p.ACKBits&0x0000FF00 != 0x0000FF00 {
		flag = flag.Toggle(FlagB)
	}
	if p.ACKBits&0x00FF0000 != 0x00FF0000 {
		flag = flag.Toggle(FlagC)
	}
	if p.ACKBits&0xFF000000 != 0xFF000000 {
		flag = flag.Toggle(FlagD)
	}
	if p.Empty {
		flag = flag.Toggle(FlagEmpty)
	}
	if p.Unordered {
		flag = flag.Toggle(FlagUnordered)
	}

	diff := int(p.Sequence) - int(p.ACK)
	if diff < 0 {
		diff += 65536
	}
	if diff <= 255 {
		flag = flag.Toggle(FlagACKEncoded)
	}

	// If the difference between the sequence number and the latest ACK'd sequence number can be represented by a
	// single byte, then represent it as a single byte and set the 5th bit of flag.

	// Marshal the flag and sequence number and latest ACK'd sequence number.

	dst = flag.AppendTo(dst)

	if p.Unordered {
		dst = bytesutil.AppendUint16BE(dst, p.ACK)
	} else {
		dst = bytesutil.AppendUint16BE(dst, p.Sequence)

		if diff <= 255 {
			dst = append(dst, uint8(diff))
		} else {
			dst = bytesutil.AppendUint16BE(dst, p.ACK)
		}
	}

	// Marshal ACK bitset.

	if p.ACKBits&0x000000FF != 0x000000FF {
		dst = append(dst, uint8(p.ACKBits&0x000000FF))
	}
	if p.ACKBits&0x0000FF00 != 0x0000FF00 {
		dst = append(dst, uint8((p.ACKBits&0x0000FF00)>>8))
	}
	if p.ACKBits&0x00FF0000 != 0x00FF0000 {
		dst = append(dst, uint8((p.ACKBits&0x00FF0000)>>16))
	}
	if p.ACKBits&0xFF000000 != 0xFF000000 {
		dst = append(dst, uint8((p.ACKBits&0xFF000000)>>24))
	}

	return dst
}

func UnmarshalPacketHeader(buf []byte) (header PacketHeader, leftover []byte, err error) {
	flag := PacketHeaderFlag(0)

	// Read first 3 bytes (header, flag).

	if len(buf) < 3 {
		return header, buf, io.ErrUnexpectedEOF
	}

	flag, buf = PacketHeaderFlag(buf[0]), buf[1:]

	if flag.Toggled(FlagFragment) {
		return header, buf, io.ErrUnexpectedEOF
	}

	header.Empty = flag.Toggled(FlagEmpty)
	header.Unordered = flag.Toggled(FlagUnordered)

	if header.Unordered {
		if len(buf) < 2 {
			return header, buf, io.ErrUnexpectedEOF
		}
		header.ACK, buf = bytesutil.Uint16BE(buf[:2]), buf[2:]
	} else {
		header.Sequence, buf = bytesutil.Uint16BE(buf[:2]), buf[2:]

		// Read and decode the latest ACK'ed sequence number (either 1 or 2 bytes) using the RLE flag marker.

		if flag.Toggled(FlagACKEncoded) {
			if len(buf) < 1 {
				return header, buf, io.ErrUnexpectedEOF
			}
			header.ACK, buf = header.Sequence-uint16(buf[0]), buf[1:]
		} else {
			if len(buf) < 2 {
				return header, buf, io.ErrUnexpectedEOF
			}
			header.ACK, buf = bytesutil.Uint16BE(buf[:2]), buf[2:]
		}
	}

	if len(buf) < bits.OnesCount8(uint8(flag&(FlagA|FlagB|FlagC|FlagD))) {
		return header, buf, io.ErrUnexpectedEOF
	}

	// Read and decode ACK bitset using the RLE flag marker.

	header.ACKBits = 0xFFFFFFFF

	if flag.Toggled(FlagA) {
		header.ACKBits &= 0xFFFFFF00
		header.ACKBits |= uint32(buf[0])
		buf = buf[1:]
	}

	if flag.Toggled(FlagB) {
		header.ACKBits &= 0xFFFF00FF
		header.ACKBits |= uint32(buf[0]) << 8
		buf = buf[1:]
	}

	if flag.Toggled(FlagC) {
		header.ACKBits &= 0xFF00FFFF
		header.ACKBits |= uint32(buf[0]) << 16
		buf = buf[1:]
	}

	if flag.Toggled(FlagD) {
		header.ACKBits &= 0x00FFFFFF
		header.ACKBits |= uint32(buf[0]) << 24
		buf = buf[1:]
	}

	return header, buf, nil
}


================================================
FILE: packet_test.go
================================================
package reliable

import (
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/valyala/bytebufferpool"
	"math"
	"testing"
	"testing/quick"
)

func TestEncodeDecodePacketHeader(t *testing.T) {
	buf := bytebufferpool.Get()
	defer bytebufferpool.Put(buf)

	f := func(seq, ack uint16, ackBits uint32) bool {
		header := PacketHeader{Sequence: seq, ACK: ack, ACKBits: ackBits}
		recovered, leftover, err := UnmarshalPacketHeader(header.AppendTo(buf.B[:0]))
		return assert.NoError(t, err) && assert.Len(t, leftover, 0) && assert.EqualValues(t, header, recovered)
	}

	require.NoError(t, quick.Check(f, &quick.Config{MaxCount: 1000}))
}

func BenchmarkMarshalPacketHeader(b *testing.B) {
	header := PacketHeader{Sequence: math.MaxUint16, ACK: math.MaxUint16, ACKBits: math.MaxUint32}

	buf := bytebufferpool.Get()
	defer bytebufferpool.Put(buf)

	b.ResetTimer()
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		buf.B = header.AppendTo(buf.B[:0])
	}
}

func BenchmarkUnmarshalPacketHeader(b *testing.B) {
	header := PacketHeader{Sequence: math.MaxUint16, ACK: math.MaxUint16, ACKBits: math.MaxUint32}

	buf := bytebufferpool.Get()
	defer bytebufferpool.Put(buf)

	buf.B = header.AppendTo(buf.B)

	var (
		recovered PacketHeader
		leftover  []byte
		err       error
	)

	b.ResetTimer()
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		recovered, leftover, err = UnmarshalPacketHeader(buf.B)
		if err != nil {
			b.Fatalf("failed to unmarshal packet header: %s", err)
		}
		if leftover := len(leftover); leftover != 0 {
			b.Fatalf("got %d byte(s) leftover", leftover)
		}
		if recovered.Sequence != header.Sequence || recovered.ACK != header.ACK || recovered.ACKBits != header.ACKBits {
			b.Fatalf("got %#v, expected %#v", recovered, header)
		}
	}

	_ = recovered
}


================================================
FILE: pool.go
================================================
package reliable

import "math"

var (
	emptyBufferIndexCache [math.MaxUint16]uint32
)

func init() {
	emptyBufferIndexCache[0] = math.MaxUint32
	for i := 1; i < math.MaxUint16; i *= 2 {
		copy(emptyBufferIndexCache[i:], emptyBufferIndexCache[:i])
	}
}

func emptyBufferIndices(indices []uint32) {
	copy(indices[:], emptyBufferIndexCache[:len(indices)])
}


================================================
FILE: protocol.go
================================================
package reliable

import (
	"fmt"
	"github.com/lithdew/seq"
	"io"
	"sync"
	"time"
)

type ProtocolPacketHandler func(buf []byte, seq uint16)
type ProtocolErrorHandler func(err error)

type Protocol struct {
	writeBufferSize uint16 // write buffer size that must be a divisor of 65536
	readBufferSize  uint16 // read buffer size that must be a divisor of 65536

	updatePeriod  time.Duration // how often time-dependant parts of the protocol get checked
	resendTimeout time.Duration // how long we wait until unacked packets should be resent

	pool *Pool

	ph ProtocolPacketHandler
	eh ProtocolErrorHandler

	mu   sync.Mutex    // mutex over everything
	die  bool          // is this conn closed?
	exit chan struct{} // signal channel to close the conn

	lui uint16    // last sent packet index that hasn't been sent via an ack yet
	oui uint16    // oldest sent packet index that hasn't been acked yet
	ouc sync.Cond // stop writes if the next write given oui may flood our peers read buffer
	ls  time.Time // last time data was sent to our peer

	wi uint16 // write index
	ri uint16 // read index

	wq []uint32 // write queue
	rq []uint32 // read queue

	wqe []writtenPacket // write queue entries
}

func NewProtocol(opts ...ProtocolOption) *Protocol {
	p := &Protocol{exit: make(chan struct{})}

	for _, opt := range opts {
		opt.applyProtocol(p)
	}

	if p.writeBufferSize == 0 {
		p.writeBufferSize = DefaultWriteBufferSize
	}

	if p.readBufferSize == 0 {
		p.readBufferSize = DefaultReadBufferSize
	}

	if p.resendTimeout == 0 {
		p.resendTimeout = DefaultResendTimeout
	}

	if p.updatePeriod == 0 {
		p.updatePeriod = DefaultUpdatePeriod
	}

	if p.pool == nil {
		p.pool = new(Pool)
	}

	p.wq = make([]uint32, p.writeBufferSize)
	p.rq = make([]uint32, p.readBufferSize)

	emptyBufferIndices(p.wq)
	emptyBufferIndices(p.rq)

	p.wqe = make([]writtenPacket, p.writeBufferSize)

	p.ouc.L = &p.mu

	return p
}

func (p *Protocol) WritePacket(reliable bool, buf []byte) ([]byte, error) {
	p.mu.Lock()
	defer p.mu.Unlock()

	var (
		idx     uint16
		ack     uint16
		ackBits uint32
		ok      = true
	)

	if reliable {
		idx, ack, ackBits, ok = p.waitForNextWriteDetails()
	} else {
		ack, ackBits = p.nextAckDetails()
	}

	if !ok {
		return nil, io.EOF
	}

	p.trackAcked(ack)

	// log.Printf("%v: send    (seq=%05d) (ack=%05d) (ack_bits=%032b) (size=%d) (reliable=%t)", &p, idx, ack, ackBits, len(buf), reliable)

	return p.write(PacketHeader{Sequence: idx, ACK: ack, ACKBits: ackBits, Unordered: !reliable}, buf), nil
}

func (p *Protocol) waitUntilReaderAvailable() {
	for !p.die && seq.GT(p.wi+1, p.oui+uint16(len(p.rq))) {
		p.ouc.Wait()
	}
}

func (p *Protocol) waitForNextWriteDetails() (idx uint16, ack uint16, ackBits uint32, ok bool) {
	p.waitUntilReaderAvailable()

	idx, ok = p.nextWriteIndex(), !p.die
	ack, ackBits = p.nextAckDetails()
	return idx, ack, ackBits, ok
}

func (p *Protocol) nextWriteIndex() (idx uint16) {
	idx, p.wi = p.wi, p.wi+1
	return idx
}

func (p *Protocol) nextAckDetails() (ack uint16, ackBits uint32) {
	ack = p.ri - 1
	ackBits = p.prepareAckBits(ack)
	return ack, ackBits
}

func (p *Protocol) prepareAckBits(ack uint16) (ackBits uint32) {
	for i, m := uint16(0), uint32(1); i < ACKBitsetSize; i, m = i+1, m<<1 {
		if p.rq[(ack-i)%uint16(len(p.rq))] != uint32(ack-i) {
			continue
		}

		ackBits |= m
	}
	return ackBits
}

func (p *Protocol) write(header PacketHeader, buf []byte) []byte {
	b := p.pool.Get()

	b.B = header.AppendTo(b.B)
	b.B = append(b.B, buf...)

	if header.Unordered {
		defer p.pool.Put(b)
	}

	if !header.Unordered {
		p.trackWrite(header.Sequence, b)
	}

	return b.B
}

func (p *Protocol) trackWrite(idx uint16, buf *Buffer) {
	if seq.GT(idx+1, p.wi) {
		p.clearWrites(p.wi, idx)
		p.wi = idx + 1
	}

	i := idx % uint16(len(p.wq))
	p.wq[i] = uint32(idx)
	if p.wqe[i].buf != nil {
		p.pool.Put(p.wqe[i].buf)
	}
	p.wqe[i].buf = buf
	p.wqe[i].acked = false
	p.wqe[i].written = time.Now()
	p.wqe[i].resent = 0
}

func (p *Protocol) clearWrites(start, end uint16) {
	count, size := end-start+1, uint16(len(p.wq))

	if count >= size {
		emptyBufferIndices(p.wq)
		return
	}

	first := p.wq[start%size:]
	length := uint16(len(first))

	if count <= length {
		emptyBufferIndices(first[:count])
		return
	}

	second := p.wq[:count-length]

	emptyBufferIndices(first)
	emptyBufferIndices(second)
}

func (p *Protocol) ReadPacket(header PacketHeader, buf []byte) []byte {
	p.mu.Lock()
	defer p.mu.Unlock()

	p.readAckBits(header.ACK, header.ACKBits)

	if !header.Unordered && !p.trackRead(header.Sequence) {
		return nil
	}

	p.trackUnacked()

	if header.Empty {
		return nil
	}

	if p.ph != nil {
		p.ph(buf, header.Sequence)
	}

	// log.Printf("%v: recv    (seq=%05d) (ack=%05d) (ack_bits=%032b) (size=%d) (reliable=%t)", &p, header.Sequence, header.ACK, header.ACKBits, len(buf), !header.Unordered)

	return p.writeAcksIfNecessary()
}

func (p *Protocol) createAckIfNecessary() (header PacketHeader, needed bool) {
	lui := p.lui

	for i := uint16(0); i < ACKBitsetSize; i++ {
		if p.rq[(lui+i)%uint16(len(p.rq))] != uint32(lui+i) {
			return header, needed
		}
	}

	lui += ACKBitsetSize
	p.lui = lui
	p.ls = time.Now()

	p.waitUntilReaderAvailable()

	header.Sequence, header.ACK = p.nextWriteIndex(), lui-1
	header.ACKBits = p.prepareAckBits(header.ACK)
	header.Empty = true

	needed = !p.die

	return header, needed
}

func (p *Protocol) writeAcksIfNecessary() []byte {
	for {
		header, needed := p.createAckIfNecessary()
		if !needed {
			return nil
		}

		// log.Printf("%v: ack     (seq=%05d) (ack=%05d) (ack_bits=%032b)", &p, header.Sequence, header.ACK, header.ACKBits)

		return p.write(header, nil)
	}
}

func (p *Protocol) readAckBits(ack uint16, ackBits uint32) {
	for idx := uint16(0); idx < ACKBitsetSize; idx, ackBits = idx+1, ackBits>>1 {
		if ackBits&1 == 0 {
			continue
		}

		i := (ack - idx) % uint16(len(p.wq))
		if p.wq[i] != uint32(ack-idx) || p.wqe[i].acked {
			continue
		}

		if p.wqe[i].buf != nil {
			p.pool.Put(p.wqe[i].buf)
		}

		p.wqe[i].buf = nil
		p.wqe[i].acked = true
	}
}

func (p *Protocol) trackRead(idx uint16) bool {
	i := idx % uint16(len(p.rq))

	if p.rq[i] == uint32(idx) { // duplicate packet
		return false
	}

	if seq.GT(idx+1, p.ri) {
		p.clearReads(p.ri, idx)
		p.ri = idx + 1
	}

	p.rq[i] = uint32(idx)

	return true
}

func (p *Protocol) clearReads(start, end uint16) {
	count, size := end-start+1, uint16(len(p.rq))

	if count >= size {
		emptyBufferIndices(p.rq)
		return
	}

	first := p.rq[start%size:]
	length := uint16(len(first))

	if count <= length {
		emptyBufferIndices(first[:count])
		return
	}

	second := p.rq[:count-length]

	emptyBufferIndices(first)
	emptyBufferIndices(second)
}

func (p *Protocol) trackAcked(ack uint16) {
	lui := p.lui

	for lui <= ack {
		if p.rq[lui%uint16(len(p.rq))] != uint32(lui) {
			break
		}
		lui++
	}

	p.lui = lui
	p.ls = time.Now()
}

func (p *Protocol) trackUnacked() {
	oui := p.oui

	for {
		i := oui % uint16(len(p.wq))
		if p.wq[i] != uint32(oui) || !p.wqe[i].acked {
			break
		}
		oui++
	}
	p.oui = oui

	p.ouc.Broadcast()
}

func (p *Protocol) close() bool {
	if p.die {
		return false
	}
	close(p.exit)
	p.die = true
	p.ouc.Broadcast()

	return true
}

func (p *Protocol) Close() {
	p.mu.Lock()
	defer p.mu.Unlock()

	if !p.close() {
		return
	}
}

func (p *Protocol) Run(transmit transmitFunc) {
	ticker := time.NewTicker(p.updatePeriod)
	defer ticker.Stop()

	for {
		select {
		case <-p.exit:
			return
		case <-ticker.C:
			if err := p.retransmitUnackedPackets(transmit); err != nil && p.eh != nil {
				p.eh(err)
			}
		}
	}
}

func (p *Protocol) retransmitUnackedPackets(transmit transmitFunc) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	for idx := uint16(0); idx < uint16(len(p.wq)); idx++ {
		i := (p.oui + idx) % uint16(len(p.wq))
		if p.wq[i] != uint32(p.oui+idx) || !p.wqe[i].shouldResend(time.Now(), p.resendTimeout) {
			continue
		}

		// log.Printf("%v: resend  (seq=%d)", &p, p.oui+idx)

		if isEOF, err := transmit(p.wqe[i].buf.B); err != nil {
			return fmt.Errorf("failed to retransmit unacked packet: %w", err)
		} else if isEOF {
			break
		}

		p.wqe[i].written = time.Now()
		p.wqe[i].resent++
	}

	return nil
}


================================================
FILE: protocol_test.go
================================================
package reliable

import (
	"github.com/stretchr/testify/require"
	"go.uber.org/goleak"
	"sync"
	"testing"
)

func testConnWaitForWriteDetails(inc uint16) func(t testing.TB) {
	return func(t testing.TB) {
		defer goleak.VerifyNone(t)

		p := NewProtocol()
		p.wi = uint16(len(p.rq))

		var wg sync.WaitGroup
		wg.Add(8)

		ch := make(chan uint16, 8)

		for i := 0; i < 8; i++ {
			go func() {
				defer wg.Done()

				p.ouc.L.Lock()
				idx, _, _, _ := p.waitForNextWriteDetails()
				p.ouc.L.Unlock()
				ch <- idx
			}()
		}

		for i := 0; i < 8; i++ {
			p.ouc.L.Lock()
			p.oui += inc
			p.ouc.Broadcast()
			p.ouc.L.Unlock()
		}

		wg.Wait()

		expected := make(map[uint16]struct{}, 8)

		close(ch)
		for idx := range ch {
			expected[idx] = struct{}{}
		}

		for i := 0; i < 8; i++ {
			actual := uint16(len(p.wq) + i)
			require.Contains(t, expected, actual)
			delete(expected, actual)
		}
	}
}

func TestConnWaitForWriteDetails(t *testing.T) {
	testConnWaitForWriteDetails(1)(t)
	testConnWaitForWriteDetails(2)(t)
	testConnWaitForWriteDetails(4)(t)
}
Download .txt
gitextract_f9yxkokx/

├── .gitignore
├── LICENSE
├── README.md
├── conn.go
├── conn_test.go
├── endpoint.go
├── endpoint_test.go
├── error.go
├── examples/
│   ├── basic/
│   │   └── main.go
│   └── benchmark/
│       └── main.go
├── fuzz.go
├── go.mod
├── go.sum
├── options.go
├── packet.go
├── packet_test.go
├── pool.go
├── protocol.go
└── protocol_test.go
Download .txt
SYMBOL INDEX (134 symbols across 14 files)

FILE: conn.go
  type transmitFunc (line 9) | type transmitFunc
  type Conn (line 11) | type Conn struct
    method WriteReliablePacket (line 22) | func (c *Conn) WriteReliablePacket(buf []byte) error {
    method WriteUnreliablePacket (line 32) | func (c *Conn) WriteUnreliablePacket(buf []byte) error {
    method Read (line 42) | func (c *Conn) Read(header PacketHeader, buf []byte) error {
    method Close (line 53) | func (c *Conn) Close() {
    method Run (line 57) | func (c *Conn) Run() {
    method transmit (line 61) | func (c *Conn) transmit(buf []byte) (EOF bool, err error) {
  function NewConn (line 17) | func NewConn(addr net.Addr, conn net.PacketConn, opts ...ProtocolOption)...

FILE: conn_test.go
  function TestConnWriteReliablePacket (line 14) | func TestConnWriteReliablePacket(t *testing.T) {
  function readLoop (line 54) | func readLoop(t *testing.T, pc net.PacketConn, c *Conn) {

FILE: endpoint.go
  type EndpointPacketHandler (line 12) | type EndpointPacketHandler
  type EndpointErrorHandler (line 13) | type EndpointErrorHandler
  type Endpoint (line 15) | type Endpoint struct
    method getConn (line 67) | func (e *Endpoint) getConn(addr net.Addr) *Conn {
    method clearConn (line 101) | func (e *Endpoint) clearConn(addr net.Addr) {
    method clearConns (line 112) | func (e *Endpoint) clearConns() {
    method Addr (line 126) | func (e *Endpoint) Addr() net.Addr {
    method WriteReliablePacket (line 130) | func (e *Endpoint) WriteReliablePacket(buf []byte, addr net.Addr) error {
    method WriteUnreliablePacket (line 138) | func (e *Endpoint) WriteUnreliablePacket(buf []byte, addr net.Addr) er...
    method Listen (line 146) | func (e *Endpoint) Listen() {
    method Close (line 201) | func (e *Endpoint) Close() error {
  function NewEndpoint (line 37) | func NewEndpoint(conn net.PacketConn, opts ...EndpointOption) *Endpoint {

FILE: endpoint_test.go
  function newPacketConn (line 16) | func newPacketConn(t testing.TB, addr string) net.PacketConn {
  function BenchmarkEndpointWriteReliablePacket (line 23) | func BenchmarkEndpointWriteReliablePacket(b *testing.B) {
  function BenchmarkEndpointWriteUnreliablePacket (line 56) | func BenchmarkEndpointWriteUnreliablePacket(b *testing.B) {
  function TestEndpointWriteReliablePacket (line 89) | func TestEndpointWriteReliablePacket(t *testing.T) {
  function TestEndpointWriteReliablePacketEndToEnd (line 147) | func TestEndpointWriteReliablePacketEndToEnd(t *testing.T) {
  function TestRaceConditions (line 192) | func TestRaceConditions(t *testing.T) {
  type testRaceConditions (line 309) | type testRaceConditions struct
    method done (line 320) | func (t *testRaceConditions) done() {
    method wait (line 326) | func (t *testRaceConditions) wait() {
    method append (line 330) | func (t *testRaceConditions) append(buf []byte) {
  function newTestRaceConditions (line 316) | func newTestRaceConditions(cap int) *testRaceConditions {
  function genNumSlice (line 338) | func genNumSlice(len int) (s []int) {
  function uniqSort (line 345) | func uniqSort(s []int) (result []int) {

FILE: error.go
  function isEOF (line 9) | func isEOF(err error) bool {

FILE: examples/basic/main.go
  function check (line 23) | func check(err error) {
  function listen (line 29) | func listen(addr string) net.PacketConn {
  function handler (line 35) | func handler(buf []byte, _ net.Addr) {
  function main (line 43) | func main() {

FILE: examples/benchmark/main.go
  function check (line 19) | func check(err error) {
  function listen (line 25) | func listen(addr string) net.PacketConn {
  function main (line 34) | func main() {

FILE: fuzz.go
  function Fuzz (line 12) | func Fuzz(data []byte) int {

FILE: options.go
  constant DefaultWriteBufferSize (line 6) | DefaultWriteBufferSize uint16 = 256
  constant DefaultReadBufferSize (line 7) | DefaultReadBufferSize  uint16 = 256
  constant DefaultUpdatePeriod (line 9) | DefaultUpdatePeriod  = 100 * time.Millisecond
  constant DefaultResendTimeout (line 10) | DefaultResendTimeout = 100 * time.Millisecond
  type ProtocolOption (line 13) | type ProtocolOption interface
  type EndpointOption (line 17) | type EndpointOption interface
  type Option (line 21) | type Option interface
  type withBufferPool (line 26) | type withBufferPool struct
    method applyProtocol (line 28) | func (o withBufferPool) applyProtocol(p *Protocol) { p.pool = o.pool }
    method applyEndpoint (line 29) | func (o withBufferPool) applyEndpoint(e *Endpoint) { e.pool = o.pool }
  function WithBufferPool (line 31) | func WithBufferPool(pool *Pool) Option { return withBufferPool{pool: poo...
  type withWriteBufferSize (line 33) | type withWriteBufferSize struct
    method applyProtocol (line 35) | func (o withWriteBufferSize) applyProtocol(p *Protocol) { p.writeBuffe...
    method applyEndpoint (line 36) | func (o withWriteBufferSize) applyEndpoint(e *Endpoint) { e.writeBuffe...
  function WithWriteBufferSize (line 38) | func WithWriteBufferSize(writeBufferSize uint16) Option {
  type withReadBufferSize (line 45) | type withReadBufferSize struct
    method applyProtocol (line 47) | func (o withReadBufferSize) applyProtocol(p *Protocol) { p.readBufferS...
    method applyEndpoint (line 48) | func (o withReadBufferSize) applyEndpoint(e *Endpoint) { e.readBufferS...
  function WithReadBufferSize (line 50) | func WithReadBufferSize(readBufferSize uint16) Option {
  type withProtocolPacketHandler (line 57) | type withProtocolPacketHandler struct
    method applyProtocol (line 60) | func (o withProtocolPacketHandler) applyProtocol(p *Protocol) { p.ph =...
  type withEndpointPacketHandler (line 58) | type withEndpointPacketHandler struct
    method applyEndpoint (line 61) | func (o withEndpointPacketHandler) applyEndpoint(e *Endpoint) { e.ph =...
  function WithProtocolPacketHandler (line 63) | func WithProtocolPacketHandler(ph ProtocolPacketHandler) ProtocolOption {
  function WithEndpointPacketHandler (line 66) | func WithEndpointPacketHandler(ph EndpointPacketHandler) EndpointOption {
  type withProtocolErrorHandler (line 70) | type withProtocolErrorHandler struct
    method applyProtocol (line 73) | func (o withProtocolErrorHandler) applyProtocol(p *Protocol) { p.eh = ...
  type withEndpointErrorHandler (line 71) | type withEndpointErrorHandler struct
    method applyEndpoint (line 74) | func (o withEndpointErrorHandler) applyEndpoint(e *Endpoint) { e.eh = ...
  function WithProtocolErrorHandler (line 76) | func WithProtocolErrorHandler(eh ProtocolErrorHandler) ProtocolOption {
  function WithEndpointErrorHandler (line 79) | func WithEndpointErrorHandler(eh EndpointErrorHandler) EndpointOption {
  type withUpdatePeriod (line 83) | type withUpdatePeriod struct
    method applyProtocol (line 85) | func (o withUpdatePeriod) applyProtocol(p *Protocol) { p.updatePeriod ...
    method applyEndpoint (line 86) | func (o withUpdatePeriod) applyEndpoint(e *Endpoint) { e.updatePeriod ...
  function WithUpdatePeriod (line 88) | func WithUpdatePeriod(updatePeriod time.Duration) Option {
  type withResendTimeout (line 95) | type withResendTimeout struct
    method applyProtocol (line 97) | func (o withResendTimeout) applyProtocol(p *Protocol) { p.resendTimeou...
    method applyEndpoint (line 98) | func (o withResendTimeout) applyEndpoint(e *Endpoint) { e.resendTimeou...
  function WithResendTimeout (line 100) | func WithResendTimeout(resendTimeout time.Duration) Option {

FILE: packet.go
  constant ACKBitsetSize (line 11) | ACKBitsetSize = 32
  type writtenPacket (line 18) | type writtenPacket struct
    method shouldResend (line 25) | func (p writtenPacket) shouldResend(now time.Time, resendTimeout time....
  type PacketHeaderFlag (line 29) | type PacketHeaderFlag
    method Toggle (line 42) | func (p PacketHeaderFlag) Toggle(flag PacketHeaderFlag) PacketHeaderFl...
    method Toggled (line 46) | func (p PacketHeaderFlag) Toggled(flag PacketHeaderFlag) bool {
    method AppendTo (line 50) | func (p PacketHeaderFlag) AppendTo(dst []byte) []byte {
  constant FlagFragment (line 32) | FlagFragment PacketHeaderFlag = 1 << iota
  constant FlagA (line 33) | FlagA
  constant FlagB (line 34) | FlagB
  constant FlagC (line 35) | FlagC
  constant FlagD (line 36) | FlagD
  constant FlagACKEncoded (line 37) | FlagACKEncoded
  constant FlagEmpty (line 38) | FlagEmpty
  constant FlagUnordered (line 39) | FlagUnordered
  type PacketHeader (line 54) | type PacketHeader struct
    method AppendTo (line 62) | func (p PacketHeader) AppendTo(dst []byte) []byte {
  function UnmarshalPacketHeader (line 130) | func UnmarshalPacketHeader(buf []byte) (header PacketHeader, leftover []...

FILE: packet_test.go
  function TestEncodeDecodePacketHeader (line 12) | func TestEncodeDecodePacketHeader(t *testing.T) {
  function BenchmarkMarshalPacketHeader (line 25) | func BenchmarkMarshalPacketHeader(b *testing.B) {
  function BenchmarkUnmarshalPacketHeader (line 39) | func BenchmarkUnmarshalPacketHeader(b *testing.B) {

FILE: pool.go
  function init (line 9) | func init() {
  function emptyBufferIndices (line 16) | func emptyBufferIndices(indices []uint32) {

FILE: protocol.go
  type ProtocolPacketHandler (line 11) | type ProtocolPacketHandler
  type ProtocolErrorHandler (line 12) | type ProtocolErrorHandler
  type Protocol (line 14) | type Protocol struct
    method WritePacket (line 84) | func (p *Protocol) WritePacket(reliable bool, buf []byte) ([]byte, err...
    method waitUntilReaderAvailable (line 112) | func (p *Protocol) waitUntilReaderAvailable() {
    method waitForNextWriteDetails (line 118) | func (p *Protocol) waitForNextWriteDetails() (idx uint16, ack uint16, ...
    method nextWriteIndex (line 126) | func (p *Protocol) nextWriteIndex() (idx uint16) {
    method nextAckDetails (line 131) | func (p *Protocol) nextAckDetails() (ack uint16, ackBits uint32) {
    method prepareAckBits (line 137) | func (p *Protocol) prepareAckBits(ack uint16) (ackBits uint32) {
    method write (line 148) | func (p *Protocol) write(header PacketHeader, buf []byte) []byte {
    method trackWrite (line 165) | func (p *Protocol) trackWrite(idx uint16, buf *Buffer) {
    method clearWrites (line 182) | func (p *Protocol) clearWrites(start, end uint16) {
    method ReadPacket (line 204) | func (p *Protocol) ReadPacket(header PacketHeader, buf []byte) []byte {
    method createAckIfNecessary (line 229) | func (p *Protocol) createAckIfNecessary() (header PacketHeader, needed...
    method writeAcksIfNecessary (line 253) | func (p *Protocol) writeAcksIfNecessary() []byte {
    method readAckBits (line 266) | func (p *Protocol) readAckBits(ack uint16, ackBits uint32) {
    method trackRead (line 286) | func (p *Protocol) trackRead(idx uint16) bool {
    method clearReads (line 303) | func (p *Protocol) clearReads(start, end uint16) {
    method trackAcked (line 325) | func (p *Protocol) trackAcked(ack uint16) {
    method trackUnacked (line 339) | func (p *Protocol) trackUnacked() {
    method close (line 354) | func (p *Protocol) close() bool {
    method Close (line 365) | func (p *Protocol) Close() {
    method Run (line 374) | func (p *Protocol) Run(transmit transmitFunc) {
    method retransmitUnackedPackets (line 390) | func (p *Protocol) retransmitUnackedPackets(transmit transmitFunc) err...
  function NewProtocol (line 44) | func NewProtocol(opts ...ProtocolOption) *Protocol {

FILE: protocol_test.go
  function testConnWaitForWriteDetails (line 10) | func testConnWaitForWriteDetails(inc uint16) func(t testing.TB) {
  function TestConnWaitForWriteDetails (line 57) | func TestConnWaitForWriteDetails(t *testing.T) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (65K chars).
[
  {
    "path": ".gitignore",
    "chars": 57,
    "preview": ".idea/\ncorpus/\ncrashers/\nsuppressions/\nreliable-fuzz.zip\n"
  },
  {
    "path": "LICENSE",
    "chars": 1090,
    "preview": "MIT License\n\nCopyright (c) 2020 Kenta Iwasaki <kenta@lithdew.net>\n\nPermission is hereby granted, free of charge, to any "
  },
  {
    "path": "README.md",
    "chars": 12430,
    "preview": "# reliable\n\n[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](LICENSE)\n[![go.dev reference](https://i"
  },
  {
    "path": "conn.go",
    "chars": 1278,
    "preview": "package reliable\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n)\n\ntype transmitFunc func(buf []byte) (bool, error)\n\ntype Conn struct {\n\t"
  },
  {
    "path": "conn_test.go",
    "chars": 1496,
    "preview": "package reliable\n\nimport (\n\t\"bytes\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/goleak\"\n\t\"math\"\n\t\"net\"\n\t\"sync/a"
  },
  {
    "path": "endpoint.go",
    "chars": 3439,
    "preview": "package reliable\n\nimport (\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n)\n\ntype EndpointPacketHandler func(buf []"
  },
  {
    "path": "endpoint_test.go",
    "chars": 7944,
    "preview": "package reliable\n\nimport (\n\t\"bytes\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/goleak\"\n\t\"net\"\n\t\"sort\"\n\t\"strcon"
  },
  {
    "path": "error.go",
    "chars": 330,
    "preview": "package reliable\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"net\"\n)\n\nfunc isEOF(err error) bool {\n\tif errors.Is(err, io.EOF) {\n\t\treturn "
  },
  {
    "path": "examples/basic/main.go",
    "chars": 1999,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"github.com/davecgh/go-spew/spew\"\n\t\"github.com/lithdew/reliable\"\n\t\"io\"\n\t\"log\""
  },
  {
    "path": "examples/benchmark/main.go",
    "chars": 1753,
    "preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"github.com/lithdew/reliable\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"sync/atomic\"\n\t\"ti"
  },
  {
    "path": "fuzz.go",
    "chars": 1213,
    "preview": "// +build gofuzz\n\npackage reliable\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"net\"\n\t\"time\"\n)\n\nfunc Fuzz(data []byte) int {\n\tca, err "
  },
  {
    "path": "go.mod",
    "chars": 529,
    "preview": "module github.com/lithdew/reliable\n\ngo 1.14\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1\n\tgithub.com/dvyukov/go-fuzz v0"
  },
  {
    "path": "go.sum",
    "chars": 6060,
    "preview": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.m"
  },
  {
    "path": "options.go",
    "chars": 3565,
    "preview": "package reliable\n\nimport \"time\"\n\nconst (\n\tDefaultWriteBufferSize uint16 = 256\n\tDefaultReadBufferSize  uint16 = 256\n\n\tDef"
  },
  {
    "path": "packet.go",
    "chars": 4602,
    "preview": "package reliable\n\nimport (\n\t\"github.com/lithdew/bytesutil\"\n\t\"github.com/valyala/bytebufferpool\"\n\t\"io\"\n\t\"math/bits\"\n\t\"tim"
  },
  {
    "path": "packet_test.go",
    "chars": 1798,
    "preview": "package reliable\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/val"
  },
  {
    "path": "pool.go",
    "chars": 356,
    "preview": "package reliable\n\nimport \"math\"\n\nvar (\n\temptyBufferIndexCache [math.MaxUint16]uint32\n)\n\nfunc init() {\n\temptyBufferIndexC"
  },
  {
    "path": "protocol.go",
    "chars": 8198,
    "preview": "package reliable\n\nimport (\n\t\"fmt\"\n\t\"github.com/lithdew/seq\"\n\t\"io\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype ProtocolPacketHandler func(buf"
  },
  {
    "path": "protocol_test.go",
    "chars": 1060,
    "preview": "package reliable\n\nimport (\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/goleak\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc testC"
  }
]

About this extraction

This page contains the full source code of the lithdew/reliable GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (57.8 KB), approximately 19.1k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!