Files

1150 lines
28 KiB
Go

// The MIT License (MIT)
//
// Copyright (c) 2015 xtaci
//
// 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.
package kcp
import (
"container/heap"
"encoding/binary"
"sync/atomic"
"time"
)
const (
IKCP_RTO_NDL = 30 // no delay min rto
IKCP_RTO_MIN = 100 // normal min rto
IKCP_RTO_DEF = 200
IKCP_RTO_MAX = 60000
IKCP_CMD_PUSH = 81 // cmd: push data
IKCP_CMD_ACK = 82 // cmd: ack
IKCP_CMD_WASK = 83 // cmd: window probe (ask)
IKCP_CMD_WINS = 84 // cmd: window size (tell)
IKCP_ASK_SEND = 1 // need to send IKCP_CMD_WASK
IKCP_ASK_TELL = 2 // need to send IKCP_CMD_WINS
IKCP_WND_SND = 32
IKCP_WND_RCV = 32
IKCP_MTU_DEF = 1400
IKCP_ACK_FAST = 3
IKCP_INTERVAL = 100
IKCP_OVERHEAD = 24
IKCP_DEADLINK = 20
IKCP_THRESH_INIT = 2
IKCP_THRESH_MIN = 2
IKCP_PROBE_INIT = 500 // 500ms to probe window size
IKCP_PROBE_LIMIT = 120000 // up to 120 secs to probe window
IKCP_SN_OFFSET = 12
)
type PacketType int8
const (
IKCP_PACKET_REGULAR PacketType = iota
IKCP_PACKET_FEC
)
type FlushType int8
const (
IKCP_FLUSH_ACKONLY FlushType = 1 << iota
IKCP_FLUSH_FULL
)
type KCPLogType int32
const (
IKCP_LOG_OUTPUT KCPLogType = 1 << iota
IKCP_LOG_INPUT
IKCP_LOG_SEND
IKCP_LOG_RECV
IKCP_LOG_OUT_ACK
IKCP_LOG_OUT_PUSH
IKCP_LOG_OUT_WASK
IKCP_LOG_OUT_WINS
IKCP_LOG_IN_ACK
IKCP_LOG_IN_PUSH
IKCP_LOG_IN_WASK
IKCP_LOG_IN_WINS
)
const (
IKCP_LOG_OUTPUT_ALL = IKCP_LOG_OUTPUT | IKCP_LOG_OUT_ACK | IKCP_LOG_OUT_PUSH | IKCP_LOG_OUT_WASK | IKCP_LOG_OUT_WINS
IKCP_LOG_INPUT_ALL = IKCP_LOG_INPUT | IKCP_LOG_IN_ACK | IKCP_LOG_IN_PUSH | IKCP_LOG_IN_WASK | IKCP_LOG_IN_WINS
IKCP_LOG_ALL = IKCP_LOG_OUTPUT_ALL | IKCP_LOG_INPUT_ALL | IKCP_LOG_SEND | IKCP_LOG_RECV
)
// monotonic reference time point
var refTime time.Time = time.Now()
// currentMs returns current elapsed monotonic milliseconds since program startup
func currentMs() uint32 { return uint32(time.Since(refTime) / time.Millisecond) }
// output_callback is a prototype which ought capture conn and call conn.Write
type output_callback func(buf []byte, size int)
// logoutput_callback is a prototype which logging kcp trace output
type logoutput_callback func(msg string, args ...any)
/* encode 8 bits unsigned int */
func ikcp_encode8u(p []byte, c byte) []byte {
p[0] = c
return p[1:]
}
/* decode 8 bits unsigned int */
func ikcp_decode8u(p []byte, c *byte) []byte {
*c = p[0]
return p[1:]
}
/* encode 16 bits unsigned int (lsb) */
func ikcp_encode16u(p []byte, w uint16) []byte {
binary.LittleEndian.PutUint16(p, w)
return p[2:]
}
/* decode 16 bits unsigned int (lsb) */
func ikcp_decode16u(p []byte, w *uint16) []byte {
*w = binary.LittleEndian.Uint16(p)
return p[2:]
}
/* encode 32 bits unsigned int (lsb) */
func ikcp_encode32u(p []byte, l uint32) []byte {
binary.LittleEndian.PutUint32(p, l)
return p[4:]
}
/* decode 32 bits unsigned int (lsb) */
func ikcp_decode32u(p []byte, l *uint32) []byte {
*l = binary.LittleEndian.Uint32(p)
return p[4:]
}
func _imin_(a, b uint32) uint32 {
if a <= b {
return a
}
return b
}
func _imax_(a, b uint32) uint32 {
if a >= b {
return a
}
return b
}
func _ibound_(lower, middle, upper uint32) uint32 {
return _imin_(_imax_(lower, middle), upper)
}
func _itimediff(later, earlier uint32) int32 {
return (int32)(later - earlier)
}
// segment defines a KCP segment
type segment struct {
conv uint32
cmd uint8
frg uint8
wnd uint16
ts uint32
sn uint32
una uint32
rto uint32
xmit uint32
resendts uint32
fastack uint32
acked uint32 // mark if the seg has acked
data []byte
}
// encode a segment into buffer
func (seg *segment) encode(ptr []byte) []byte {
ptr = ikcp_encode32u(ptr, seg.conv)
ptr = ikcp_encode8u(ptr, seg.cmd)
ptr = ikcp_encode8u(ptr, seg.frg)
ptr = ikcp_encode16u(ptr, seg.wnd)
ptr = ikcp_encode32u(ptr, seg.ts)
ptr = ikcp_encode32u(ptr, seg.sn)
ptr = ikcp_encode32u(ptr, seg.una)
ptr = ikcp_encode32u(ptr, uint32(len(seg.data)))
atomic.AddUint64(&DefaultSnmp.OutSegs, 1)
return ptr
}
// segmentHeap is a min-heap of segments, used for receiving segments in order
type segmentHeap struct {
segments []segment
marks map[uint32]struct{} // to avoid duplicates
}
func newSegmentHeap() *segmentHeap {
h := &segmentHeap{
marks: make(map[uint32]struct{}),
}
heap.Init(h)
return h
}
func (h *segmentHeap) Len() int { return len(h.segments) }
func (h *segmentHeap) Less(i, j int) bool {
return _itimediff(h.segments[j].sn, h.segments[i].sn) > 0
}
func (h *segmentHeap) Swap(i, j int) { h.segments[i], h.segments[j] = h.segments[j], h.segments[i] }
func (h *segmentHeap) Push(x any) {
h.segments = append(h.segments, x.(segment))
h.marks[x.(segment).sn] = struct{}{}
}
func (h *segmentHeap) Pop() any {
n := len(h.segments)
x := h.segments[n-1]
h.segments = h.segments[0 : n-1]
delete(h.marks, x.sn)
return x
}
func (h *segmentHeap) Has(sn uint32) bool {
_, exists := h.marks[sn]
return exists
}
// KCP defines a single KCP connection
type KCP struct {
conv, mtu, mss, state uint32
snd_una, snd_nxt, rcv_nxt uint32
ssthresh uint32
rx_rttvar, rx_srtt int32
rx_rto, rx_minrto uint32
snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe uint32
interval, ts_flush uint32
nodelay, updated uint32
ts_probe, probe_wait uint32
dead_link, incr uint32
fastresend int32
nocwnd, stream int32
logmask KCPLogType
snd_queue *RingBuffer[segment]
rcv_queue *RingBuffer[segment]
snd_buf *RingBuffer[segment]
rcv_buf *segmentHeap
acklist []ackItem
buffer []byte
output output_callback
log logoutput_callback
}
type ackItem struct {
sn uint32
ts uint32
}
// NewKCP create a new kcp state machine
//
// 'conv' must be equal in the connection peers, or else data will be silently rejected.
//
// 'output' function will be called whenever these is data to be sent on wire.
func NewKCP(conv uint32, output output_callback) *KCP {
kcp := new(KCP)
kcp.conv = conv
kcp.snd_wnd = IKCP_WND_SND
kcp.rcv_wnd = IKCP_WND_RCV
kcp.rmt_wnd = IKCP_WND_RCV
kcp.mtu = IKCP_MTU_DEF
kcp.mss = kcp.mtu - IKCP_OVERHEAD
kcp.buffer = make([]byte, (kcp.mtu+IKCP_OVERHEAD)*3)
kcp.rx_rto = IKCP_RTO_DEF
kcp.rx_minrto = IKCP_RTO_MIN
kcp.interval = IKCP_INTERVAL
kcp.ts_flush = IKCP_INTERVAL
kcp.ssthresh = IKCP_THRESH_INIT
kcp.dead_link = IKCP_DEADLINK
kcp.output = output
kcp.snd_buf = NewRingBuffer[segment](IKCP_WND_SND * 2)
kcp.rcv_queue = NewRingBuffer[segment](IKCP_WND_RCV * 2)
kcp.snd_queue = NewRingBuffer[segment](IKCP_WND_SND * 2)
kcp.rcv_buf = newSegmentHeap()
return kcp
}
// newSegment creates a KCP segment
func (kcp *KCP) newSegment(size int) (seg segment) {
seg.data = defaultBufferPool.Get()[:size]
return
}
// recycleSegment recycles a KCP segment
func (kcp *KCP) recycleSegment(seg *segment) {
if seg.data != nil {
defaultBufferPool.Put(seg.data)
seg.data = nil
}
}
// PeekSize checks the size of next message in the recv queue
func (kcp *KCP) PeekSize() (length int) {
seg, ok := kcp.rcv_queue.Peek()
if !ok {
return -1
}
if seg.frg == 0 {
return len(seg.data)
}
if kcp.rcv_queue.Len() < int(seg.frg+1) {
return -1
}
for seg := range kcp.rcv_queue.ForEach {
length += len(seg.data)
if seg.frg == 0 {
break
}
}
return
}
// Receive data from kcp state machine
//
// Return number of bytes read.
//
// Return -1 when there is no readable data.
//
// Return -2 if len(buffer) is smaller than kcp.PeekSize().
func (kcp *KCP) Recv(buffer []byte) (n int) {
peeksize := kcp.PeekSize()
if peeksize < 0 {
return -1
}
if peeksize > len(buffer) {
return -2
}
var fast_recover bool
if kcp.rcv_queue.Len() >= int(kcp.rcv_wnd) {
fast_recover = true
}
// merge fragment
for {
seg, ok := kcp.rcv_queue.Pop()
if !ok {
break
}
copy(buffer, seg.data)
buffer = buffer[len(seg.data):]
n += len(seg.data)
kcp.recycleSegment(&seg)
if seg.frg == 0 {
kcp.debugLog(IKCP_LOG_RECV, "stream", kcp.stream, "conv", kcp.conv, "sn", seg.sn, "ts", seg.ts, "datalen", n)
break
}
}
// move available data from rcv_buf -> rcv_queue
for kcp.rcv_buf.Len() > 0 {
seg := heap.Pop(kcp.rcv_buf).(segment)
if seg.sn == kcp.rcv_nxt && kcp.rcv_queue.Len() < int(kcp.rcv_wnd) {
kcp.rcv_queue.Push(seg)
kcp.rcv_nxt++
} else {
// push back segment
heap.Push(kcp.rcv_buf, seg)
break
}
}
// fast recover
if kcp.rcv_queue.Len() < int(kcp.rcv_wnd) && fast_recover {
// ready to send back IKCP_CMD_WINS in ikcp_flush
// tell remote my window size
kcp.probe |= IKCP_ASK_TELL
}
return
}
// Send is user/upper level send, returns below zero for error
func (kcp *KCP) Send(buffer []byte) int {
var count int
if len(buffer) == 0 {
return -1
}
kcp.debugLog(IKCP_LOG_SEND, "stream", kcp.stream, "conv", kcp.conv, "datalen", len(buffer))
// append to previous segment in streaming mode (if possible)
if kcp.stream != 0 {
if n := kcp.snd_queue.Len(); n > 0 {
for seg := range kcp.snd_queue.ForEachReverse {
if len(seg.data) < int(kcp.mss) {
capacity := int(kcp.mss) - len(seg.data)
extend := min(len(buffer), capacity)
// grow slice, the underlying cap is guaranteed to
// be larger than kcp.mss
oldlen := len(seg.data)
seg.data = seg.data[:oldlen+extend]
copy(seg.data[oldlen:], buffer)
buffer = buffer[extend:]
}
break
}
}
if len(buffer) == 0 {
return 0
}
}
if len(buffer) <= int(kcp.mss) {
count = 1
} else {
count = (len(buffer) + int(kcp.mss) - 1) / int(kcp.mss)
}
if count > 255 {
return -2
}
if count == 0 {
count = 1
}
for i := 0; i < count; i++ {
var size int
size = min(len(buffer), int(kcp.mss))
seg := kcp.newSegment(size)
copy(seg.data, buffer[:size])
if kcp.stream == 0 { // message mode
seg.frg = uint8(count - i - 1)
} else { // stream mode
seg.frg = 0
}
kcp.snd_queue.Push(seg)
buffer = buffer[size:]
}
return 0
}
func (kcp *KCP) update_ack(rtt int32) {
// https://tools.ietf.org/html/rfc6298
var rto uint32
if kcp.rx_srtt == 0 {
kcp.rx_srtt = rtt
kcp.rx_rttvar = rtt >> 1
} else {
delta := rtt - kcp.rx_srtt
kcp.rx_srtt += delta >> 3
if delta < 0 {
delta = -delta
}
if rtt < kcp.rx_srtt-kcp.rx_rttvar {
// if the new RTT sample is below the bottom of the range of
// what an RTT measurement is expected to be.
// give an 8x reduced weight versus its normal weighting
kcp.rx_rttvar += (delta - kcp.rx_rttvar) >> 5
} else {
kcp.rx_rttvar += (delta - kcp.rx_rttvar) >> 2
}
}
rto = uint32(kcp.rx_srtt) + _imax_(kcp.interval, uint32(kcp.rx_rttvar)<<2)
kcp.rx_rto = _ibound_(kcp.rx_minrto, rto, IKCP_RTO_MAX)
}
func (kcp *KCP) shrink_buf() {
if seg, ok := kcp.snd_buf.Peek(); ok {
kcp.snd_una = seg.sn
} else {
kcp.snd_una = kcp.snd_nxt
}
}
func (kcp *KCP) parse_ack(sn uint32) {
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
return
}
for seg := range kcp.snd_buf.ForEach {
if sn == seg.sn {
// mark and free space, but leave the segment here,
// and wait until `una` to delete this, then we don't
// have to shift the segments behind forward,
// which is an expensive operation for large window
seg.acked = 1
kcp.recycleSegment(seg)
break
}
if _itimediff(sn, seg.sn) < 0 {
break
}
}
}
func (kcp *KCP) parse_fastack(sn, ts uint32) int {
shouldFastAck := 0
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
return 0
}
for seg := range kcp.snd_buf.ForEach {
if _itimediff(sn, seg.sn) < 0 {
break
} else if sn != seg.sn && _itimediff(seg.ts, ts) <= 0 {
if seg.fastack != 0xFFFFFFFF {
seg.fastack++
if seg.fastack >= uint32(kcp.fastresend) {
shouldFastAck = 1
}
}
}
}
return shouldFastAck
}
func (kcp *KCP) parse_una(una uint32) int {
count := 0
for seg := range kcp.snd_buf.ForEach {
if _itimediff(una, seg.sn) > 0 {
kcp.recycleSegment(seg)
count++
} else {
break
}
}
kcp.snd_buf.Discard(count)
return count
}
// ack append
func (kcp *KCP) ack_push(sn, ts uint32) {
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
}
// returns true if data has repeated
func (kcp *KCP) parse_data(newseg segment) bool {
sn := newseg.sn
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
_itimediff(sn, kcp.rcv_nxt) < 0 {
return true
}
repeat := false
if !kcp.rcv_buf.Has(sn) {
// replicate the content if it's new
dataCopy := defaultBufferPool.Get()[:len(newseg.data)]
copy(dataCopy, newseg.data)
newseg.data = dataCopy
// insert the new segment into rcv_buf
heap.Push(kcp.rcv_buf, newseg)
} else {
repeat = true
}
// move available data from rcv_buf -> rcv_queue
for kcp.rcv_buf.Len() > 0 {
seg := heap.Pop(kcp.rcv_buf).(segment)
if seg.sn == kcp.rcv_nxt && kcp.rcv_queue.Len() < int(kcp.rcv_wnd) {
kcp.rcv_queue.Push(seg)
kcp.rcv_nxt++
} else {
// push back segment
heap.Push(kcp.rcv_buf, seg)
break
}
}
return repeat
}
// Input a packet into kcp state machine.
//
// 'regular' indicates it's a real data packet from remote, and it means it's not generated from ReedSolomon
// codecs.
//
// 'ackNoDelay' will trigger immediate ACK, but surely it will not be efficient in bandwidth
func (kcp *KCP) Input(data []byte, pktType PacketType, ackNoDelay bool) int {
snd_una := kcp.snd_una
if len(data) < IKCP_OVERHEAD {
return -1
}
var latest uint32 // the latest ack packet
var updateRTT int
var inSegs uint64
var flushSegments int // signal to flush segments
for {
var ts, sn, length, una, conv uint32
var wnd uint16
var cmd, frg uint8
if len(data) < int(IKCP_OVERHEAD) {
break
}
data = ikcp_decode32u(data, &conv)
if conv != kcp.conv {
return -1
}
data = ikcp_decode8u(data, &cmd)
data = ikcp_decode8u(data, &frg)
data = ikcp_decode16u(data, &wnd)
data = ikcp_decode32u(data, &ts)
data = ikcp_decode32u(data, &sn)
data = ikcp_decode32u(data, &una)
data = ikcp_decode32u(data, &length)
kcp.debugLog(IKCP_LOG_INPUT, "conv", conv, "cmd", cmd, "frg", frg, "wnd", wnd, "ts", ts, "sn", sn, "una", una, "len", length, "datalen", len(data))
if len(data) < int(length) {
return -2
}
if cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK &&
cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS {
return -3
}
// only trust window updates from regular packets. i.e: latest update
if pktType == IKCP_PACKET_REGULAR {
kcp.rmt_wnd = uint32(wnd)
}
if kcp.parse_una(una) > 0 {
flushSegments |= 1
}
kcp.shrink_buf()
if cmd == IKCP_CMD_ACK {
kcp.debugLog(IKCP_LOG_IN_ACK, "conv", conv, "sn", sn, "una", una, "ts", ts, "rto", kcp.rx_rto)
kcp.parse_ack(sn)
flushSegments |= kcp.parse_fastack(sn, ts)
updateRTT |= 1
latest = ts
} else if cmd == IKCP_CMD_PUSH {
repeat := true
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) < 0 {
kcp.ack_push(sn, ts)
if _itimediff(sn, kcp.rcv_nxt) >= 0 {
var seg segment
seg.conv = conv
seg.cmd = cmd
seg.frg = frg
seg.wnd = wnd
seg.ts = ts
seg.sn = sn
seg.una = una
seg.data = data[:length] // delayed data copying
repeat = kcp.parse_data(seg)
}
}
if pktType == IKCP_PACKET_REGULAR && repeat {
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
}
kcp.debugLog(IKCP_LOG_IN_PUSH, "conv", conv, "sn", sn, "una", una, "ts", ts, "packettype", pktType, "repeat", repeat)
} else if cmd == IKCP_CMD_WASK {
// ready to send back IKCP_CMD_WINS in Ikcp_flush
// tell remote my window size
kcp.probe |= IKCP_ASK_TELL
kcp.debugLog(IKCP_LOG_IN_WASK, "conv", conv, "wnd", wnd, "ts", ts)
} else if cmd == IKCP_CMD_WINS {
kcp.debugLog(IKCP_LOG_IN_WINS, "conv", conv, "wnd", wnd, "ts", ts)
} else {
return -3
}
inSegs++
data = data[length:]
}
atomic.AddUint64(&DefaultSnmp.InSegs, inSegs)
// update rtt with the latest ts
// ignore the FEC packet
if updateRTT != 0 && pktType == IKCP_PACKET_REGULAR {
current := currentMs()
if _itimediff(current, latest) >= 0 {
kcp.update_ack(_itimediff(current, latest))
}
}
// cwnd update when packet arrived
if kcp.nocwnd == 0 {
if _itimediff(kcp.snd_una, snd_una) > 0 {
if kcp.cwnd < kcp.rmt_wnd {
mss := kcp.mss
if kcp.cwnd < kcp.ssthresh {
kcp.cwnd++
kcp.incr += mss
} else {
if kcp.incr < mss {
kcp.incr = mss
}
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
if (kcp.cwnd+1)*mss <= kcp.incr {
if mss > 0 {
kcp.cwnd = (kcp.incr + mss - 1) / mss
} else {
kcp.cwnd = kcp.incr + mss - 1
}
}
}
if kcp.cwnd > kcp.rmt_wnd {
kcp.cwnd = kcp.rmt_wnd
kcp.incr = kcp.rmt_wnd * mss
}
}
}
}
// Determine if we need to flush data segments or acks
if flushSegments != 0 {
// If window has slided or, a fastack should be triggered,
// Flush immediately. In previous implementations, we only
// send out fastacks when interval timeouts, so the resending packets
// have to wait until then. Now, we try to flush as soon as we can.
kcp.flush(IKCP_FLUSH_FULL)
} else if len(kcp.acklist) >= int(kcp.mtu/IKCP_OVERHEAD) { // clocking
// This serves as the clock for low-latency network.(i.e. the latency is less than the interval.)
// If the other end is waiting for confirmations, it has to want until the interval timeouts then
// the flush() is triggered to send out the una & acks. In low-latency network, the interval time is too long to wait,
// so acks have to be sent out immediately when there are too many.
kcp.flush(IKCP_FLUSH_ACKONLY)
} else if ackNoDelay && len(kcp.acklist) > 0 { // testing(xtaci): ack immediately if acNoDelay is set
kcp.flush(IKCP_FLUSH_ACKONLY)
}
return 0
}
func (kcp *KCP) wnd_unused() uint16 {
if kcp.rcv_queue.Len() < int(kcp.rcv_wnd) {
return uint16(int(kcp.rcv_wnd) - kcp.rcv_queue.Len())
}
return 0
}
// flush pending data
func (kcp *KCP) flush(flushType FlushType) (nextUpdate uint32) {
var seg segment
seg.conv = kcp.conv
seg.cmd = IKCP_CMD_ACK
seg.wnd = kcp.wnd_unused()
seg.una = kcp.rcv_nxt
buffer := kcp.buffer
ptr := buffer
// makeSpace makes room for writing
makeSpace := func(space int) {
size := len(buffer) - len(ptr)
if size+space > int(kcp.mtu) {
kcp.output(buffer, size)
ptr = buffer
}
}
// flush bytes in buffer if there is any
flushBuffer := func() {
size := len(buffer) - len(ptr)
if size > 0 {
kcp.output(buffer, size)
}
}
defer func() {
flushBuffer()
atomic.StoreUint64(&DefaultSnmp.RingBufferSndQueue, uint64(kcp.snd_queue.Len()))
atomic.StoreUint64(&DefaultSnmp.RingBufferRcvQueue, uint64(kcp.rcv_queue.Len()))
atomic.StoreUint64(&DefaultSnmp.RingBufferSndBuffer, uint64(kcp.snd_buf.Len()))
}()
/*
* flush acknowledges
*/
if flushType == IKCP_FLUSH_ACKONLY || flushType == IKCP_FLUSH_FULL {
for i, ack := range kcp.acklist {
makeSpace(IKCP_OVERHEAD)
// filter jitters caused by bufferbloat
if _itimediff(ack.sn, kcp.rcv_nxt) >= 0 || len(kcp.acklist)-1 == i {
seg.sn, seg.ts = ack.sn, ack.ts
ptr = seg.encode(ptr)
kcp.debugLog(IKCP_LOG_OUT_ACK, "conv", seg.conv, "sn", seg.sn, "una", seg.una, "ts", seg.ts)
}
}
kcp.acklist = kcp.acklist[0:0]
}
// probe window size (if remote window size equals zero)
if kcp.rmt_wnd == 0 {
current := currentMs()
if kcp.probe_wait == 0 {
kcp.probe_wait = IKCP_PROBE_INIT
kcp.ts_probe = current + kcp.probe_wait
} else {
if _itimediff(current, kcp.ts_probe) >= 0 {
if kcp.probe_wait < IKCP_PROBE_INIT {
kcp.probe_wait = IKCP_PROBE_INIT
}
kcp.probe_wait += kcp.probe_wait / 2
if kcp.probe_wait > IKCP_PROBE_LIMIT {
kcp.probe_wait = IKCP_PROBE_LIMIT
}
kcp.ts_probe = current + kcp.probe_wait
kcp.probe |= IKCP_ASK_SEND
}
}
} else {
kcp.ts_probe = 0
kcp.probe_wait = 0
}
/*
* flush window probing commands
*/
if (kcp.probe & IKCP_ASK_SEND) != 0 {
seg.cmd = IKCP_CMD_WASK
makeSpace(IKCP_OVERHEAD)
ptr = seg.encode(ptr)
kcp.debugLog(IKCP_LOG_OUT_WASK, "conv", seg.conv, "wnd", seg.wnd, "ts", seg.ts)
}
// flush window probing commands
if (kcp.probe & IKCP_ASK_TELL) != 0 {
seg.cmd = IKCP_CMD_WINS
makeSpace(IKCP_OVERHEAD)
ptr = seg.encode(ptr)
kcp.debugLog(IKCP_LOG_OUT_WINS, "conv", seg.conv, "wnd", seg.wnd, "ts", seg.ts)
}
kcp.probe = 0
// calculate window size
cwnd := _imin_(kcp.snd_wnd, kcp.rmt_wnd)
if kcp.nocwnd == 0 {
cwnd = _imin_(kcp.cwnd, cwnd)
}
// sliding window, controlled by snd_nxt && sna_una+cwnd
newSegsCount := 0
for {
if _itimediff(kcp.snd_nxt, kcp.snd_una+cwnd) >= 0 {
break
}
newseg, ok := kcp.snd_queue.Pop()
if !ok {
break
}
newseg.conv = kcp.conv
newseg.cmd = IKCP_CMD_PUSH
newseg.sn = kcp.snd_nxt
kcp.snd_buf.Push(newseg)
kcp.snd_nxt++
newSegsCount++
}
// calculate resent
resent := uint32(kcp.fastresend)
if kcp.fastresend <= 0 {
resent = 0xffffffff
}
/*
* flush segments
*/
current := currentMs()
var change, lostSegs, fastRetransSegs, earlyRetransSegs uint64
nextUpdate = kcp.interval
if flushType == IKCP_FLUSH_FULL {
for segment := range kcp.snd_buf.ForEach {
needsend := false
if segment.acked == 1 {
continue
}
if segment.xmit == 0 { // initial transmit
needsend = true
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
} else if segment.fastack >= resent && segment.fastack != 0xFFFFFFFF { // fast retransmit
needsend = true
segment.fastack = 0xFFFFFFFF // must wait until RTO to reset
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
change++
fastRetransSegs++
} else if segment.fastack > 0 && segment.fastack != 0xFFFFFFFF && newSegsCount == 0 { // early retransmit
needsend = true
segment.fastack = 0xFFFFFFFF
segment.rto = kcp.rx_rto
segment.resendts = current + segment.rto
change++
earlyRetransSegs++
} else if _itimediff(current, segment.resendts) >= 0 { // RTO
needsend = true
if kcp.nodelay == 0 {
segment.rto += kcp.rx_rto
} else {
segment.rto += kcp.rx_rto / 2
}
segment.fastack = 0
segment.resendts = current + segment.rto
lostSegs++
}
if needsend {
current = currentMs()
segment.xmit++
segment.ts = current
segment.wnd = seg.wnd
segment.una = seg.una
need := IKCP_OVERHEAD + len(segment.data)
makeSpace(need)
ptr = segment.encode(ptr)
copy(ptr, segment.data)
ptr = ptr[len(segment.data):]
kcp.debugLog(IKCP_LOG_OUT_PUSH, "conv", segment.conv, "sn", segment.sn, "frg", segment.frg, "una", segment.una, "ts", segment.ts, "xmit", segment.xmit, "datalen", len(segment.data))
if segment.xmit >= kcp.dead_link {
kcp.state = 0xFFFFFFFF
}
}
// get the nearest rto
if rto := _itimediff(segment.resendts, current); rto > 0 && uint32(rto) < nextUpdate {
nextUpdate = uint32(rto)
}
}
}
// counter updates
sum := lostSegs
if lostSegs > 0 {
atomic.AddUint64(&DefaultSnmp.LostSegs, lostSegs)
}
if fastRetransSegs > 0 {
atomic.AddUint64(&DefaultSnmp.FastRetransSegs, fastRetransSegs)
sum += fastRetransSegs
}
if earlyRetransSegs > 0 {
atomic.AddUint64(&DefaultSnmp.EarlyRetransSegs, earlyRetransSegs)
sum += earlyRetransSegs
}
if sum > 0 {
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
}
// cwnd update
if kcp.nocwnd == 0 {
// update ssthresh
// rate halving, https://tools.ietf.org/html/rfc6937
if change > 0 {
inflight := kcp.snd_nxt - kcp.snd_una
kcp.ssthresh = max(inflight/2, IKCP_THRESH_MIN)
kcp.cwnd = kcp.ssthresh + resent
kcp.incr = kcp.cwnd * kcp.mss
}
// congestion control, https://tools.ietf.org/html/rfc5681
if lostSegs > 0 {
kcp.ssthresh = max(cwnd/2, IKCP_THRESH_MIN)
kcp.cwnd = 1
kcp.incr = kcp.mss
}
if kcp.cwnd < 1 {
kcp.cwnd = 1
kcp.incr = kcp.mss
}
}
return nextUpdate
}
// (deprecated)
//
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
// ikcp_check when to call it again (without ikcp_input/_send calling).
// 'current' - current timestamp in millisec.
func (kcp *KCP) Update() {
var slap int32
current := currentMs()
if kcp.updated == 0 {
kcp.updated = 1
kcp.ts_flush = current
}
slap = _itimediff(current, kcp.ts_flush)
if slap >= 10000 || slap < -10000 {
kcp.ts_flush = current
slap = 0
}
if slap >= 0 {
kcp.ts_flush += kcp.interval
if _itimediff(current, kcp.ts_flush) >= 0 {
kcp.ts_flush = current + kcp.interval
}
kcp.flush(IKCP_FLUSH_FULL)
}
}
// (deprecated)
//
// Check determines when should you invoke ikcp_update:
// returns when you should invoke ikcp_update in millisec, if there
// is no ikcp_input/_send calling. you can call ikcp_update in that
// time, instead of call update repeatly.
// Important to reduce unnacessary ikcp_update invoking. use it to
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
// or optimize ikcp_update when handling massive kcp connections)
func (kcp *KCP) Check() uint32 {
current := currentMs()
ts_flush := kcp.ts_flush
tm_flush := int32(0x7fffffff)
tm_packet := int32(0x7fffffff)
minimal := uint32(0)
if kcp.updated == 0 {
return current
}
if _itimediff(current, ts_flush) >= 10000 ||
_itimediff(current, ts_flush) < -10000 {
ts_flush = current
}
if _itimediff(current, ts_flush) >= 0 {
return current
}
tm_flush = _itimediff(ts_flush, current)
for seg := range kcp.snd_buf.ForEach {
diff := _itimediff(seg.resendts, current)
if diff <= 0 {
return current
}
if diff < tm_packet {
tm_packet = diff
}
}
minimal = uint32(tm_packet)
if tm_packet >= tm_flush {
minimal = uint32(tm_flush)
}
if minimal >= kcp.interval {
minimal = kcp.interval
}
return current + minimal
}
// SetMtu changes MTU size, default is 1400
func (kcp *KCP) SetMtu(mtu int) int {
if mtu < 50 || mtu < IKCP_OVERHEAD {
return -1
}
kcp.mtu = uint32(mtu)
kcp.mss = kcp.mtu - IKCP_OVERHEAD
kcp.buffer = make([]byte, (mtu+IKCP_OVERHEAD)*3)
return 0
}
// NoDelay options
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
// nodelay: 0:disable(default), 1:enable
// interval: internal update timer interval in millisec, default is 100ms
// resend: 0:disable fast resend(default), 1:enable fast resend
// nc: 0:normal congestion control(default), 1:disable congestion control
func (kcp *KCP) NoDelay(nodelay, interval, resend, nc int) int {
if nodelay >= 0 {
kcp.nodelay = uint32(nodelay)
if nodelay != 0 {
kcp.rx_minrto = IKCP_RTO_NDL
} else {
kcp.rx_minrto = IKCP_RTO_MIN
}
}
if interval >= 0 {
if interval > 5000 {
interval = 5000
} else if interval < 10 {
interval = 10
}
kcp.interval = uint32(interval)
}
if resend >= 0 {
kcp.fastresend = int32(resend)
}
if nc >= 0 {
kcp.nocwnd = int32(nc)
}
return 0
}
// WndSize sets maximum window size: sndwnd=32, rcvwnd=32 by default
func (kcp *KCP) WndSize(sndwnd, rcvwnd int) int {
if sndwnd > 0 {
kcp.snd_wnd = uint32(sndwnd)
}
if rcvwnd > 0 {
kcp.rcv_wnd = uint32(rcvwnd)
}
return 0
}
// WaitSnd gets how many packet is waiting to be sent
func (kcp *KCP) WaitSnd() int {
return kcp.snd_buf.Len() + kcp.snd_queue.Len()
}
// SetLogger configures the trace logger
func (kcp *KCP) SetLogger(mask KCPLogType, logger logoutput_callback) {
if logger == nil {
kcp.logmask = 0
return
}
kcp.logmask = mask
kcp.log = logger
}