| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042 | 
							- // Kcp based on https://github.com/skywind3000/kcp
 
- // Kept as close to original as possible.
 
- using System;
 
- using System.Collections.Generic;
 
- namespace kcp2k
 
- {
 
-     public class Kcp
 
-     {
 
-         // original Kcp has a define option, which is not defined by default:
 
-         // #define FASTACK_CONSERVE
 
-         public const int RTO_NDL = 30;             // no delay min rto
 
-         public const int RTO_MIN = 100;            // normal min rto
 
-         public const int RTO_DEF = 200;            // default RTO
 
-         public const int RTO_MAX = 60000;          // maximum RTO
 
-         public const int CMD_PUSH = 81;            // cmd: push data
 
-         public const int CMD_ACK  = 82;            // cmd: ack
 
-         public const int CMD_WASK = 83;            // cmd: window probe (ask)
 
-         public const int CMD_WINS = 84;            // cmd: window size (tell)
 
-         public const int ASK_SEND = 1;             // need to send CMD_WASK
 
-         public const int ASK_TELL = 2;             // need to send CMD_WINS
 
-         public const int WND_SND = 32;             // default send window
 
-         public const int WND_RCV = 128;            // default receive window. must be >= max fragment size
 
-         public const int MTU_DEF = 1200;           // default MTU (reduced to 1200 to fit all cases: https://en.wikipedia.org/wiki/Maximum_transmission_unit ; steam uses 1200 too!)
 
-         public const int ACK_FAST = 3;
 
-         public const int INTERVAL = 100;
 
-         public const int OVERHEAD = 24;
 
-         public const int DEADLINK = 20;
 
-         public const int THRESH_INIT = 2;
 
-         public const int THRESH_MIN = 2;
 
-         public const int PROBE_INIT = 7000;        // 7 secs to probe window size
 
-         public const int PROBE_LIMIT = 120000;     // up to 120 secs to probe window
 
-         public const int FASTACK_LIMIT = 5;        // max times to trigger fastack
 
-         internal struct AckItem
 
-         {
 
-             internal uint serialNumber;
 
-             internal uint timestamp;
 
-         }
 
-         // kcp members.
 
-         internal int state;
 
-         readonly uint conv;          // conversation
 
-         internal uint mtu;
 
-         internal uint mss;           // maximum segment size := MTU - OVERHEAD
 
-         internal uint snd_una;       // unacknowledged. e.g. snd_una is 9 it means 8 has been confirmed, 9 and 10 have been sent
 
-         internal uint snd_nxt;
 
-         internal uint rcv_nxt;
 
-         internal uint ssthresh;      // slow start threshold
 
-         internal int rx_rttval;      // average deviation of rtt, used to measure the jitter of rtt
 
-         internal int rx_srtt;        // smoothed round trip time (a weighted average of rtt)
 
-         internal int rx_rto;
 
-         internal int rx_minrto;
 
-         internal uint snd_wnd;       // send window
 
-         internal uint rcv_wnd;       // receive window
 
-         internal uint rmt_wnd;       // remote window
 
-         internal uint cwnd;          // congestion window
 
-         internal uint probe;
 
-         internal uint interval;
 
-         internal uint ts_flush;
 
-         internal uint xmit;
 
-         internal uint nodelay;       // not a bool. original Kcp has '<2 else' check.
 
-         internal bool updated;
 
-         internal uint ts_probe;      // timestamp probe
 
-         internal uint probe_wait;
 
-         internal uint dead_link;
 
-         internal uint incr;
 
-         internal uint current;       // current time (milliseconds). set by Update.
 
-         internal int fastresend;
 
-         internal int fastlimit;
 
-         internal bool nocwnd;        // no congestion window
 
-         internal readonly Queue<Segment> snd_queue = new Queue<Segment>(16); // send queue
 
-         internal readonly Queue<Segment> rcv_queue = new Queue<Segment>(16); // receive queue
 
-         // snd_buffer needs index removals.
 
-         // C# LinkedList allocates for each entry, so let's keep List for now.
 
-         internal readonly List<Segment> snd_buf = new List<Segment>(16);   // send buffer
 
-         // rcv_buffer needs index insertions and backwards iteration.
 
-         // C# LinkedList allocates for each entry, so let's keep List for now.
 
-         internal readonly List<Segment> rcv_buf = new List<Segment>(16);   // receive buffer
 
-         internal readonly List<AckItem> acklist = new List<AckItem>(16);
 
-         internal byte[] buffer;
 
-         readonly Action<byte[], int> output; // buffer, size
 
-         // get how many packet is waiting to be sent
 
-         public int WaitSnd => snd_buf.Count + snd_queue.Count;
 
-         // segment pool to avoid allocations in C#.
 
-         // this is not part of the original C code.
 
-         readonly Pool<Segment> SegmentPool = new Pool<Segment>(
 
-             // create new segment
 
-             () => new Segment(),
 
-             // reset segment before reuse
 
-             (segment) => segment.Reset(),
 
-             // initial capacity
 
-             32
 
-         );
 
-         // ikcp_create
 
-         // create a new kcp control object, 'conv' must equal in two endpoint
 
-         // from the same connection.
 
-         public Kcp(uint conv, Action<byte[], int> output)
 
-         {
 
-             this.conv = conv;
 
-             this.output = output;
 
-             snd_wnd = WND_SND;
 
-             rcv_wnd = WND_RCV;
 
-             rmt_wnd = WND_RCV;
 
-             mtu = MTU_DEF;
 
-             mss = mtu - OVERHEAD;
 
-             rx_rto = RTO_DEF;
 
-             rx_minrto = RTO_MIN;
 
-             interval = INTERVAL;
 
-             ts_flush = INTERVAL;
 
-             ssthresh = THRESH_INIT;
 
-             fastlimit = FASTACK_LIMIT;
 
-             dead_link = DEADLINK;
 
-             buffer = new byte[(mtu + OVERHEAD) * 3];
 
-         }
 
-         // ikcp_segment_new
 
-         // we keep the original function and add our pooling to it.
 
-         // this way we'll never miss it anywhere.
 
-         Segment SegmentNew() => SegmentPool.Take();
 
-         // ikcp_segment_delete
 
-         // we keep the original function and add our pooling to it.
 
-         // this way we'll never miss it anywhere.
 
-         void SegmentDelete(Segment seg) => SegmentPool.Return(seg);
 
-         // ikcp_recv
 
-         // receive data from kcp state machine
 
-         //   returns number of bytes read.
 
-         //   returns negative on error.
 
-         // note: pass negative length to peek.
 
-         public int Receive(byte[] buffer, int len)
 
-         {
 
-             // kcp's ispeek feature is not supported.
 
-             // this makes 'merge fragment' code significantly easier because
 
-             // we can iterate while queue.Count > 0 and dequeue each time.
 
-             // if we had to consider ispeek then count would always be > 0 and
 
-             // we would have to remove only after the loop.
 
-             //
 
-             //bool ispeek = len < 0;
 
-             if (len < 0)
 
-                 throw new NotSupportedException("Receive ispeek for negative len is not supported!");
 
-             if (rcv_queue.Count == 0)
 
-                 return -1;
 
-             if (len < 0) len = -len;
 
-             int peeksize = PeekSize();
 
-             if (peeksize < 0)
 
-                 return -2;
 
-             if (peeksize > len)
 
-                 return -3;
 
-             bool recover = rcv_queue.Count >= rcv_wnd;
 
-             // merge fragment.
 
-             int offset = 0;
 
-             len = 0;
 
-             // original KCP iterates rcv_queue and deletes if !ispeek.
 
-             // removing from a c# queue while iterating is not possible, but
 
-             // we can change to 'while Count > 0' and remove every time.
 
-             // (we can remove every time because we removed ispeek support!)
 
-             while (rcv_queue.Count > 0)
 
-             {
 
-                 // unlike original kcp, we dequeue instead of just getting the
 
-                 // entry. this is fine because we remove it in ANY case.
 
-                 Segment seg = rcv_queue.Dequeue();
 
-                 Buffer.BlockCopy(seg.data.GetBuffer(), 0, buffer, offset, (int)seg.data.Position);
 
-                 offset += (int)seg.data.Position;
 
-                 len += (int)seg.data.Position;
 
-                 uint fragment = seg.frg;
 
-                 // note: ispeek is not supported in order to simplify this loop
 
-                 // unlike original kcp, we don't need to remove seg from queue
 
-                 // because we already dequeued it.
 
-                 // simply delete it
 
-                 SegmentDelete(seg);
 
-                 if (fragment == 0)
 
-                     break;
 
-             }
 
-             // move available data from rcv_buf -> rcv_queue
 
-             int removed = 0;
 
-             foreach (Segment seg in rcv_buf)
 
-             {
 
-                 if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
 
-                 {
 
-                     // can't remove while iterating. remember how many to remove
 
-                     // and do it after the loop.
 
-                     // note: don't return segment. we only add it to rcv_queue
 
-                     ++removed;
 
-                     // add
 
-                     rcv_queue.Enqueue(seg);
 
-                     rcv_nxt++;
 
-                 }
 
-                 else
 
-                 {
 
-                     break;
 
-                 }
 
-             }
 
-             rcv_buf.RemoveRange(0, removed);
 
-             // fast recover
 
-             if (rcv_queue.Count < rcv_wnd && recover)
 
-             {
 
-                 // ready to send back CMD_WINS in flush
 
-                 // tell remote my window size
 
-                 probe |= ASK_TELL;
 
-             }
 
-             return len;
 
-         }
 
-         // ikcp_peeksize
 
-         // check the size of next message in the recv queue
 
-         public int PeekSize()
 
-         {
 
-             int length = 0;
 
-             if (rcv_queue.Count == 0) return -1;
 
-             Segment seq = rcv_queue.Peek();
 
-             if (seq.frg == 0) return (int)seq.data.Position;
 
-             if (rcv_queue.Count < seq.frg + 1) return -1;
 
-             foreach (Segment seg in rcv_queue)
 
-             {
 
-                 length += (int)seg.data.Position;
 
-                 if (seg.frg == 0) break;
 
-             }
 
-             return length;
 
-         }
 
-         // ikcp_send
 
-         // sends byte[] to the other end.
 
-         public int Send(byte[] buffer, int offset, int len)
 
-         {
 
-             // fragment count
 
-             int count;
 
-             if (len < 0) return -1;
 
-             // streaming mode: removed. we never want to send 'hello' and
 
-             // receive 'he' 'll' 'o'. we want to always receive 'hello'.
 
-             // calculate amount of fragments necessary for 'len'
 
-             if (len <= mss) count = 1;
 
-             else count = (int)((len + mss - 1) / mss);
 
-             // original kcp uses WND_RCV const even though rcv_wnd is the
 
-             // runtime variable. may or may not be correct, see also:
 
-             // see also: https://github.com/skywind3000/kcp/pull/291/files
 
-             if (count >= WND_RCV) return -2;
 
-             if (count == 0) count = 1;
 
-             // fragment
 
-             for (int i = 0; i < count; i++)
 
-             {
 
-                 int size = len > (int)mss ? (int)mss : len;
 
-                 Segment seg = SegmentNew();
 
-                 if (len > 0)
 
-                 {
 
-                     seg.data.Write(buffer, offset, size);
 
-                 }
 
-                 // seg.len = size: WriteBytes sets segment.Position!
 
-                 seg.frg = (byte)(count - i - 1);
 
-                 snd_queue.Enqueue(seg);
 
-                 offset += size;
 
-                 len -= size;
 
-             }
 
-             return 0;
 
-         }
 
-         // ikcp_update_ack
 
-         void UpdateAck(int rtt) // round trip time
 
-         {
 
-             // https://tools.ietf.org/html/rfc6298
 
-             if (rx_srtt == 0)
 
-             {
 
-                 rx_srtt = rtt;
 
-                 rx_rttval = rtt / 2;
 
-             }
 
-             else
 
-             {
 
-                 int delta = rtt - rx_srtt;
 
-                 if (delta < 0) delta = -delta;
 
-                 rx_rttval = (3 * rx_rttval + delta) / 4;
 
-                 rx_srtt = (7 * rx_srtt + rtt) / 8;
 
-                 if (rx_srtt < 1) rx_srtt = 1;
 
-             }
 
-             int rto = rx_srtt + Math.Max((int)interval, 4 * rx_rttval);
 
-             rx_rto = Utils.Clamp(rto, rx_minrto, RTO_MAX);
 
-         }
 
-         // ikcp_shrink_buf
 
-         internal void ShrinkBuf()
 
-         {
 
-             if (snd_buf.Count > 0)
 
-             {
 
-                 Segment seg = snd_buf[0];
 
-                 snd_una = seg.sn;
 
-             }
 
-             else
 
-             {
 
-                 snd_una = snd_nxt;
 
-             }
 
-         }
 
-         // ikcp_parse_ack
 
-         // removes the segment with 'sn' from send buffer
 
-         internal void ParseAck(uint sn)
 
-         {
 
-             if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
 
-                 return;
 
-             // for-int so we can erase while iterating
 
-             for (int i = 0; i < snd_buf.Count; ++i)
 
-             {
 
-                 Segment seg = snd_buf[i];
 
-                 if (sn == seg.sn)
 
-                 {
 
-                     snd_buf.RemoveAt(i);
 
-                     SegmentDelete(seg);
 
-                     break;
 
-                 }
 
-                 if (Utils.TimeDiff(sn, seg.sn) < 0)
 
-                 {
 
-                     break;
 
-                 }
 
-             }
 
-         }
 
-         // ikcp_parse_una
 
-         void ParseUna(uint una)
 
-         {
 
-             int removed = 0;
 
-             foreach (Segment seg in snd_buf)
 
-             {
 
-                 if (Utils.TimeDiff(una, seg.sn) > 0)
 
-                 {
 
-                     // can't remove while iterating. remember how many to remove
 
-                     // and do it after the loop.
 
-                     ++removed;
 
-                     SegmentDelete(seg);
 
-                 }
 
-                 else
 
-                 {
 
-                     break;
 
-                 }
 
-             }
 
-             snd_buf.RemoveRange(0, removed);
 
-         }
 
-         // ikcp_parse_fastack
 
-         void ParseFastack(uint sn, uint ts)
 
-         {
 
-             if (Utils.TimeDiff(sn, snd_una) < 0 || Utils.TimeDiff(sn, snd_nxt) >= 0)
 
-                 return;
 
-             foreach (Segment seg in snd_buf)
 
-             {
 
-                 if (Utils.TimeDiff(sn, seg.sn) < 0)
 
-                 {
 
-                     break;
 
-                 }
 
-                 else if (sn != seg.sn)
 
-                 {
 
- #if !FASTACK_CONSERVE
 
-                     seg.fastack++;
 
- #else
 
-                     if (Utils.TimeDiff(ts, seg.ts) >= 0)
 
-                         seg.fastack++;
 
- #endif
 
-                 }
 
-             }
 
-         }
 
-         // ikcp_ack_push
 
-         // appends an ack.
 
-         void AckPush(uint sn, uint ts)
 
-         {
 
-             acklist.Add(new AckItem{ serialNumber = sn, timestamp = ts });
 
-         }
 
-         // ikcp_parse_data
 
-         void ParseData(Segment newseg)
 
-         {
 
-             uint sn = newseg.sn;
 
-             if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) >= 0 ||
 
-                 Utils.TimeDiff(sn, rcv_nxt) < 0)
 
-             {
 
-                 SegmentDelete(newseg);
 
-                 return;
 
-             }
 
-             InsertSegmentInReceiveBuffer(newseg);
 
-             MoveReceiveBufferDataToReceiveQueue();
 
-         }
 
-         // inserts the segment into rcv_buf, ordered by seg.sn.
 
-         // drops the segment if one with the same seg.sn already exists.
 
-         // goes through receive buffer in reverse order for performance.
 
-         //
 
-         // note: see KcpTests.InsertSegmentInReceiveBuffer test!
 
-         // note: 'insert or delete' can be done in different ways, but let's
 
-         //       keep consistency with original C kcp.
 
-         internal void InsertSegmentInReceiveBuffer(Segment newseg)
 
-         {
 
-             bool repeat = false; // 'duplicate'
 
-             // original C iterates backwards, so we need to do that as well.
 
-             int i;
 
-             for (i = rcv_buf.Count - 1; i >= 0; i--)
 
-             {
 
-                 Segment seg = rcv_buf[i];
 
-                 if (seg.sn == newseg.sn)
 
-                 {
 
-                     // duplicate segment found. nothing will be added.
 
-                     repeat = true;
 
-                     break;
 
-                 }
 
-                 if (Utils.TimeDiff(newseg.sn, seg.sn) > 0)
 
-                 {
 
-                     // this entry's sn is < newseg.sn, so let's stop
 
-                     break;
 
-                 }
 
-             }
 
-             // no duplicate? then insert.
 
-             if (!repeat)
 
-             {
 
-                 rcv_buf.Insert(i + 1, newseg);
 
-             }
 
-             // duplicate. just delete it.
 
-             else
 
-             {
 
-                 SegmentDelete(newseg);
 
-             }
 
-         }
 
-         // move available data from rcv_buf -> rcv_queue
 
-         void MoveReceiveBufferDataToReceiveQueue()
 
-         {
 
-             int removed = 0;
 
-             foreach (Segment seg in rcv_buf)
 
-             {
 
-                 if (seg.sn == rcv_nxt && rcv_queue.Count < rcv_wnd)
 
-                 {
 
-                     // can't remove while iterating. remember how many to remove
 
-                     // and do it after the loop.
 
-                     ++removed;
 
-                     rcv_queue.Enqueue(seg);
 
-                     rcv_nxt++;
 
-                 }
 
-                 else
 
-                 {
 
-                     break;
 
-                 }
 
-             }
 
-             rcv_buf.RemoveRange(0, removed);
 
-         }
 
-         // ikcp_input
 
-         // used when you receive a low level packet (e.g. UDP packet)
 
-         // => original kcp uses offset=0, we made it a parameter so that high
 
-         //    level can skip the channel byte more easily
 
-         public int Input(byte[] data, int offset, int size)
 
-         {
 
-             uint prev_una = snd_una;
 
-             uint maxack = 0;
 
-             uint latest_ts = 0;
 
-             int flag = 0;
 
-             if (data == null || size < OVERHEAD) return -1;
 
-             while (true)
 
-             {
 
-                 uint ts = 0;
 
-                 uint sn = 0;
 
-                 uint len = 0;
 
-                 uint una = 0;
 
-                 uint conv_ = 0;
 
-                 ushort wnd = 0;
 
-                 byte cmd = 0;
 
-                 byte frg = 0;
 
-                 // enough data left to decode segment (aka OVERHEAD bytes)?
 
-                 if (size < OVERHEAD) break;
 
-                 // decode segment
 
-                 offset += Utils.Decode32U(data, offset, ref conv_);
 
-                 if (conv_ != conv) return -1;
 
-                 offset += Utils.Decode8u(data, offset, ref cmd);
 
-                 offset += Utils.Decode8u(data, offset, ref frg);
 
-                 offset += Utils.Decode16U(data, offset, ref wnd);
 
-                 offset += Utils.Decode32U(data, offset, ref ts);
 
-                 offset += Utils.Decode32U(data, offset, ref sn);
 
-                 offset += Utils.Decode32U(data, offset, ref una);
 
-                 offset += Utils.Decode32U(data, offset, ref len);
 
-                 // subtract the segment bytes from size
 
-                 size -= OVERHEAD;
 
-                 // enough remaining to read 'len' bytes of the actual payload?
 
-                 if (size < len || len < 0) return -2;
 
-                 if (cmd != CMD_PUSH && cmd != CMD_ACK &&
 
-                     cmd != CMD_WASK && cmd != CMD_WINS)
 
-                     return -3;
 
-                 rmt_wnd = wnd;
 
-                 ParseUna(una);
 
-                 ShrinkBuf();
 
-                 if (cmd == CMD_ACK)
 
-                 {
 
-                     if (Utils.TimeDiff(current, ts) >= 0)
 
-                     {
 
-                         UpdateAck(Utils.TimeDiff(current, ts));
 
-                     }
 
-                     ParseAck(sn);
 
-                     ShrinkBuf();
 
-                     if (flag == 0)
 
-                     {
 
-                         flag = 1;
 
-                         maxack = sn;
 
-                         latest_ts = ts;
 
-                     }
 
-                     else
 
-                     {
 
-                         if (Utils.TimeDiff(sn, maxack) > 0)
 
-                         {
 
- #if !FASTACK_CONSERVE
 
-                             maxack = sn;
 
-                             latest_ts = ts;
 
- #else
 
-                             if (Utils.TimeDiff(ts, latest_ts) > 0)
 
-                             {
 
-                                 maxack = sn;
 
-                                 latest_ts = ts;
 
-                             }
 
- #endif
 
-                         }
 
-                     }
 
-                 }
 
-                 else if (cmd == CMD_PUSH)
 
-                 {
 
-                     if (Utils.TimeDiff(sn, rcv_nxt + rcv_wnd) < 0)
 
-                     {
 
-                         AckPush(sn, ts);
 
-                         if (Utils.TimeDiff(sn, rcv_nxt) >= 0)
 
-                         {
 
-                             Segment seg = SegmentNew();
 
-                             seg.conv = conv_;
 
-                             seg.cmd = cmd;
 
-                             seg.frg = frg;
 
-                             seg.wnd = wnd;
 
-                             seg.ts = ts;
 
-                             seg.sn = sn;
 
-                             seg.una = una;
 
-                             if (len > 0)
 
-                             {
 
-                                 seg.data.Write(data, offset, (int)len);
 
-                             }
 
-                             ParseData(seg);
 
-                         }
 
-                     }
 
-                 }
 
-                 else if (cmd == CMD_WASK)
 
-                 {
 
-                     // ready to send back CMD_WINS in flush
 
-                     // tell remote my window size
 
-                     probe |= ASK_TELL;
 
-                 }
 
-                 else if (cmd == CMD_WINS)
 
-                 {
 
-                     // do nothing
 
-                 }
 
-                 else
 
-                 {
 
-                     return -3;
 
-                 }
 
-                 offset += (int)len;
 
-                 size -= (int)len;
 
-             }
 
-             if (flag != 0)
 
-             {
 
-                 ParseFastack(maxack, latest_ts);
 
-             }
 
-             // cwnd update when packet arrived
 
-             if (Utils.TimeDiff(snd_una, prev_una) > 0)
 
-             {
 
-                 if (cwnd < rmt_wnd)
 
-                 {
 
-                     if (cwnd < ssthresh)
 
-                     {
 
-                         cwnd++;
 
-                         incr += mss;
 
-                     }
 
-                     else
 
-                     {
 
-                         if (incr < mss) incr = mss;
 
-                         incr += (mss * mss) / incr + (mss / 16);
 
-                         if ((cwnd + 1) * mss <= incr)
 
-                         {
 
-                             cwnd = (incr + mss - 1) / ((mss > 0) ? mss : 1);
 
-                         }
 
-                     }
 
-                     if (cwnd > rmt_wnd)
 
-                     {
 
-                         cwnd = rmt_wnd;
 
-                         incr = rmt_wnd * mss;
 
-                     }
 
-                 }
 
-             }
 
-             return 0;
 
-         }
 
-         // ikcp_wnd_unused
 
-         uint WndUnused()
 
-         {
 
-             if (rcv_queue.Count < rcv_wnd)
 
-                 return rcv_wnd - (uint)rcv_queue.Count;
 
-             return 0;
 
-         }
 
-         // ikcp_flush
 
-         // flush remain ack segments
 
-         public void Flush()
 
-         {
 
-             int offset = 0;    // buffer ptr in original C
 
-             bool lost = false; // lost segments
 
-             // helper functions
 
-             void MakeSpace(int space)
 
-             {
 
-                 if (offset + space > mtu)
 
-                 {
 
-                     output(buffer, offset);
 
-                     offset = 0;
 
-                 }
 
-             }
 
-             void FlushBuffer()
 
-             {
 
-                 if (offset > 0)
 
-                 {
 
-                     output(buffer, offset);
 
-                 }
 
-             }
 
-             // 'ikcp_update' haven't been called.
 
-             if (!updated) return;
 
-             // kcp only stack allocates a segment here for performance, leaving
 
-             // its data buffer null because this segment's data buffer is never
 
-             // used. that's fine in C, but in C# our segment is class so we need
 
-             // to allocate and most importantly, not forget to deallocate it
 
-             // before returning.
 
-             Segment seg = SegmentNew();
 
-             seg.conv = conv;
 
-             seg.cmd = CMD_ACK;
 
-             seg.wnd = WndUnused();
 
-             seg.una = rcv_nxt;
 
-             // flush acknowledges
 
-             foreach (AckItem ack in acklist)
 
-             {
 
-                 MakeSpace(OVERHEAD);
 
-                 // ikcp_ack_get assigns ack[i] to seg.sn, seg.ts
 
-                 seg.sn = ack.serialNumber;
 
-                 seg.ts = ack.timestamp;
 
-                 offset += seg.Encode(buffer, offset);
 
-             }
 
-             acklist.Clear();
 
-             // probe window size (if remote window size equals zero)
 
-             if (rmt_wnd == 0)
 
-             {
 
-                 if (probe_wait == 0)
 
-                 {
 
-                     probe_wait = PROBE_INIT;
 
-                     ts_probe = current + probe_wait;
 
-                 }
 
-                 else
 
-                 {
 
-                     if (Utils.TimeDiff(current, ts_probe) >= 0)
 
-                     {
 
-                         if (probe_wait < PROBE_INIT)
 
-                             probe_wait = PROBE_INIT;
 
-                         probe_wait += probe_wait / 2;
 
-                         if (probe_wait > PROBE_LIMIT)
 
-                             probe_wait = PROBE_LIMIT;
 
-                         ts_probe = current + probe_wait;
 
-                         probe |= ASK_SEND;
 
-                     }
 
-                 }
 
-             }
 
-             else
 
-             {
 
-                 ts_probe = 0;
 
-                 probe_wait = 0;
 
-             }
 
-             // flush window probing commands
 
-             if ((probe & ASK_SEND) != 0)
 
-             {
 
-                 seg.cmd = CMD_WASK;
 
-                 MakeSpace(OVERHEAD);
 
-                 offset += seg.Encode(buffer, offset);
 
-             }
 
-             // flush window probing commands
 
-             if ((probe & ASK_TELL) != 0)
 
-             {
 
-                 seg.cmd = CMD_WINS;
 
-                 MakeSpace(OVERHEAD);
 
-                 offset += seg.Encode(buffer, offset);
 
-             }
 
-             probe = 0;
 
-             // calculate window size
 
-             uint cwnd_ = Math.Min(snd_wnd, rmt_wnd);
 
-             // if congestion window:
 
-             if (!nocwnd) cwnd_ = Math.Min(cwnd, cwnd_);
 
-             // move data from snd_queue to snd_buf
 
-             // sliding window, controlled by snd_nxt && sna_una+cwnd
 
-             //
 
-             // ELI5: 'snd_nxt' is what we want to send.
 
-             //       'snd_una' is what hasn't been acked yet.
 
-             //       copy up to 'cwnd_' difference between them (sliding window)
 
-             while (Utils.TimeDiff(snd_nxt, snd_una + cwnd_) < 0)
 
-             {
 
-                 if (snd_queue.Count == 0) break;
 
-                 Segment newseg = snd_queue.Dequeue();
 
-                 newseg.conv = conv;
 
-                 newseg.cmd = CMD_PUSH;
 
-                 newseg.wnd = seg.wnd;
 
-                 newseg.ts = current;
 
-                 newseg.sn = snd_nxt++;
 
-                 newseg.una = rcv_nxt;
 
-                 newseg.resendts = current;
 
-                 newseg.rto = rx_rto;
 
-                 newseg.fastack = 0;
 
-                 newseg.xmit = 0;
 
-                 snd_buf.Add(newseg);
 
-             }
 
-             // calculate resent
 
-             uint resent = fastresend > 0 ? (uint)fastresend : 0xffffffff;
 
-             uint rtomin = nodelay == 0 ? (uint)rx_rto >> 3 : 0;
 
-             // flush data segments
 
-             int change = 0;
 
-             foreach (Segment segment in snd_buf)
 
-             {
 
-                 bool needsend = false;
 
-                 // initial transmit
 
-                 if (segment.xmit == 0)
 
-                 {
 
-                     needsend = true;
 
-                     segment.xmit++;
 
-                     segment.rto = rx_rto;
 
-                     segment.resendts = current + (uint)segment.rto + rtomin;
 
-                 }
 
-                 // RTO
 
-                 else if (Utils.TimeDiff(current, segment.resendts) >= 0)
 
-                 {
 
-                     needsend = true;
 
-                     segment.xmit++;
 
-                     xmit++;
 
-                     if (nodelay == 0)
 
-                     {
 
-                         segment.rto += Math.Max(segment.rto, rx_rto);
 
-                     }
 
-                     else
 
-                     {
 
-                         int step = (nodelay < 2) ? segment.rto : rx_rto;
 
-                         segment.rto += step / 2;
 
-                     }
 
-                     segment.resendts = current + (uint)segment.rto;
 
-                     lost = true;
 
-                 }
 
-                 // fast retransmit
 
-                 else if (segment.fastack >= resent)
 
-                 {
 
-                     if (segment.xmit <= fastlimit || fastlimit <= 0)
 
-                     {
 
-                         needsend = true;
 
-                         segment.xmit++;
 
-                         segment.fastack = 0;
 
-                         segment.resendts = current + (uint)segment.rto;
 
-                         change++;
 
-                     }
 
-                 }
 
-                 if (needsend)
 
-                 {
 
-                     segment.ts = current;
 
-                     segment.wnd = seg.wnd;
 
-                     segment.una = rcv_nxt;
 
-                     int need = OVERHEAD + (int)segment.data.Position;
 
-                     MakeSpace(need);
 
-                     offset += segment.Encode(buffer, offset);
 
-                     if (segment.data.Position > 0)
 
-                     {
 
-                         Buffer.BlockCopy(segment.data.GetBuffer(), 0, buffer, offset, (int)segment.data.Position);
 
-                         offset += (int)segment.data.Position;
 
-                     }
 
-                     if (segment.xmit >= dead_link)
 
-                     {
 
-                         state = -1;
 
-                     }
 
-                 }
 
-             }
 
-             // kcp stackallocs 'seg'. our C# segment is a class though, so we
 
-             // need to properly delete and return it to the pool now that we are
 
-             // done with it.
 
-             SegmentDelete(seg);
 
-             // flash remain segments
 
-             FlushBuffer();
 
-             // update ssthresh
 
-             // rate halving, https://tools.ietf.org/html/rfc6937
 
-             if (change > 0)
 
-             {
 
-                 uint inflight = snd_nxt - snd_una;
 
-                 ssthresh = inflight / 2;
 
-                 if (ssthresh < THRESH_MIN)
 
-                     ssthresh = THRESH_MIN;
 
-                 cwnd = ssthresh + resent;
 
-                 incr = cwnd * mss;
 
-             }
 
-             // congestion control, https://tools.ietf.org/html/rfc5681
 
-             if (lost)
 
-             {
 
-                 // original C uses 'cwnd', not kcp->cwnd!
 
-                 ssthresh = cwnd_ / 2;
 
-                 if (ssthresh < THRESH_MIN)
 
-                     ssthresh = THRESH_MIN;
 
-                 cwnd = 1;
 
-                 incr = mss;
 
-             }
 
-             if (cwnd < 1)
 
-             {
 
-                 cwnd = 1;
 
-                 incr = mss;
 
-             }
 
-         }
 
-         // ikcp_update
 
-         // update state (call it repeatedly, every 10ms-100ms), or you can ask
 
-         // Check() when to call it again (without Input/Send calling).
 
-         //
 
-         // 'current' - current timestamp in millisec. pass it to Kcp so that
 
-         // Kcp doesn't have to do any stopwatch/deltaTime/etc. code
 
-         public void Update(uint currentTimeMilliSeconds)
 
-         {
 
-             current = currentTimeMilliSeconds;
 
-             if (!updated)
 
-             {
 
-                 updated = true;
 
-                 ts_flush = current;
 
-             }
 
-             int slap = Utils.TimeDiff(current, ts_flush);
 
-             if (slap >= 10000 || slap < -10000)
 
-             {
 
-                 ts_flush = current;
 
-                 slap = 0;
 
-             }
 
-             if (slap >= 0)
 
-             {
 
-                 ts_flush += interval;
 
-                 if (Utils.TimeDiff(current, ts_flush) >= 0)
 
-                 {
 
-                     ts_flush = current + interval;
 
-                 }
 
-                 Flush();
 
-             }
 
-         }
 
-         // ikcp_check
 
-         // Determine when should you invoke update
 
-         // Returns when you should invoke update in millisec, if there is no
 
-         // input/send calling. you can call update in that time, instead of
 
-         // call update repeatly.
 
-         //
 
-         // Important to reduce unnecessary update invoking. use it to schedule
 
-         // update (e.g. implementing an epoll-like mechanism, or optimize update
 
-         // when handling massive kcp connections).
 
-         public uint Check(uint current_)
 
-         {
 
-             uint ts_flush_ = ts_flush;
 
-             int tm_flush = 0x7fffffff;
 
-             int tm_packet = 0x7fffffff;
 
-             if (!updated)
 
-             {
 
-                 return current_;
 
-             }
 
-             if (Utils.TimeDiff(current_, ts_flush_) >= 10000 ||
 
-                 Utils.TimeDiff(current_, ts_flush_) < -10000)
 
-             {
 
-                 ts_flush_ = current_;
 
-             }
 
-             if (Utils.TimeDiff(current_, ts_flush_) >= 0)
 
-             {
 
-                 return current_;
 
-             }
 
-             tm_flush = Utils.TimeDiff(ts_flush_, current_);
 
-             foreach (Segment seg in snd_buf)
 
-             {
 
-                 int diff = Utils.TimeDiff(seg.resendts, current_);
 
-                 if (diff <= 0)
 
-                 {
 
-                     return current_;
 
-                 }
 
-                 if (diff < tm_packet) tm_packet = diff;
 
-             }
 
-             uint minimal = (uint)(tm_packet < tm_flush ? tm_packet : tm_flush);
 
-             if (minimal >= interval) minimal = interval;
 
-             return current_ + minimal;
 
-         }
 
-         // ikcp_setmtu
 
-         // Change MTU (Maximum Transmission Unit) size.
 
-         public void SetMtu(uint mtu)
 
-         {
 
-             if (mtu < 50 || mtu < OVERHEAD)
 
-                 throw new ArgumentException("MTU must be higher than 50 and higher than OVERHEAD");
 
-             buffer = new byte[(mtu + OVERHEAD) * 3];
 
-             this.mtu = mtu;
 
-             mss = mtu - OVERHEAD;
 
-         }
 
-         // ikcp_interval
 
-         public void SetInterval(uint interval)
 
-         {
 
-             if (interval > 5000) interval = 5000;
 
-             else if (interval < 10) interval = 10;
 
-             this.interval = interval;
 
-         }
 
-         // ikcp_nodelay
 
-         // configuration: https://github.com/skywind3000/kcp/blob/master/README.en.md#protocol-configuration
 
-         //   nodelay : Whether nodelay mode is enabled, 0 is not enabled; 1 enabled.
 
-         //   interval :Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms.
 
-         //   resend :Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission)
 
-         //   nc :Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”.
 
-         // Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0);
 
-         // Turbo Mode: ikcp_nodelay(kcp, 1, 10, 2, 1);
 
-         public void SetNoDelay(uint nodelay, uint interval = INTERVAL, int resend = 0, bool nocwnd = false)
 
-         {
 
-             this.nodelay = nodelay;
 
-             if (nodelay != 0)
 
-             {
 
-                 rx_minrto = RTO_NDL;
 
-             }
 
-             else
 
-             {
 
-                 rx_minrto = RTO_MIN;
 
-             }
 
-             if (interval >= 0)
 
-             {
 
-                 if (interval > 5000) interval = 5000;
 
-                 else if (interval < 10) interval = 10;
 
-                 this.interval = interval;
 
-             }
 
-             if (resend >= 0)
 
-             {
 
-                 fastresend = resend;
 
-             }
 
-             this.nocwnd = nocwnd;
 
-         }
 
-         // ikcp_wndsize
 
-         public void SetWindowSize(uint sendWindow, uint receiveWindow)
 
-         {
 
-             if (sendWindow > 0)
 
-             {
 
-                 snd_wnd = sendWindow;
 
-             }
 
-             if (receiveWindow > 0)
 
-             {
 
-                 // must >= max fragment size
 
-                 rcv_wnd = Math.Max(receiveWindow, WND_RCV);
 
-             }
 
-         }
 
-     }
 
- }
 
 
  |