mirror of https://github.com/status-im/nim-eth.git
Style fixes and comment improvements on uTP code (#623)
This commit is contained in:
parent
6dacb2ca5c
commit
d74dc40bee
|
@ -31,7 +31,8 @@ proc init*(T: type NodeAddress, nodeId: NodeId, address: Address): NodeAddress =
|
||||||
NodeAddress(nodeId: nodeId, address: address)
|
NodeAddress(nodeId: nodeId, address: address)
|
||||||
|
|
||||||
proc init*(T: type NodeAddress, node: Node): Option[NodeAddress] =
|
proc init*(T: type NodeAddress, node: Node): Option[NodeAddress] =
|
||||||
node.address.map((address: Address) => NodeAddress(nodeId: node.id, address: address))
|
node.address.map((address: Address) =>
|
||||||
|
NodeAddress(nodeId: node.id, address: address))
|
||||||
|
|
||||||
proc hash(x: NodeAddress): Hash =
|
proc hash(x: NodeAddress): Hash =
|
||||||
var h = 0
|
var h = 0
|
||||||
|
@ -51,25 +52,31 @@ func `$`*(x: UtpSocketKey[NodeAddress]): string =
|
||||||
", rcvId: " & $x.rcvId &
|
", rcvId: " & $x.rcvId &
|
||||||
")"
|
")"
|
||||||
|
|
||||||
proc talkReqDirect(p: protocol.Protocol, n: NodeAddress, protocol, request: seq[byte]): Future[void] =
|
proc talkReqDirect(
|
||||||
|
p: protocol.Protocol, n: NodeAddress, protocol, request: seq[byte]):
|
||||||
|
Future[void] =
|
||||||
let
|
let
|
||||||
reqId = RequestId.init(p.rng[])
|
reqId = RequestId.init(p.rng[])
|
||||||
message = encodeMessage(TalkReqMessage(protocol: protocol, request: request), reqId)
|
message = encodeMessage(
|
||||||
|
TalkReqMessage(protocol: protocol, request: request), reqId)
|
||||||
|
(data, nonce) = encodeMessagePacket(
|
||||||
|
p.rng[], p.codec, n.nodeId, n.address, message)
|
||||||
|
|
||||||
(data, nonce) = encodeMessagePacket(p.rng[], p.codec, n.nodeId, n.address, message)
|
trace "Send message packet",
|
||||||
|
dstId = n.nodeId, address = n.address, kind = MessageKind.talkreq
|
||||||
trace "Send message packet", dstId = n.nodeId, address = n.address, kind = MessageKind.talkreq
|
|
||||||
p.send(n.address, data)
|
p.send(n.address, data)
|
||||||
|
|
||||||
proc initSendCallback(
|
proc initSendCallback(
|
||||||
t: protocol.Protocol, subProtocolName: seq[byte]): SendCallback[NodeAddress] =
|
t: protocol.Protocol, subProtocolName: seq[byte]):
|
||||||
|
SendCallback[NodeAddress] =
|
||||||
return (
|
return (
|
||||||
proc (to: NodeAddress, data: seq[byte]): Future[void] =
|
proc (to: NodeAddress, data: seq[byte]): Future[void] =
|
||||||
let fut = newFuture[void]()
|
let fut = newFuture[void]()
|
||||||
# hidden assumption here is that nodes already have established discv5 session
|
# hidden assumption here is that nodes already have established discv5
|
||||||
# between each other. In our use case this should be true as opening stream
|
# session between each other. In our use case this should be true as
|
||||||
# is only done after successful OFFER/ACCEPT or FINDCONTENT/CONTENT exchange
|
# opening stream is only done after successful OFFER/ACCEPT or
|
||||||
# which forces nodes to establish session between each other.
|
# FINDCONTENT/CONTENT exchange which forces nodes to establish session
|
||||||
|
# between each other.
|
||||||
discard t.talkReqDirect(to, subProtocolName, data)
|
discard t.talkReqDirect(to, subProtocolName, data)
|
||||||
fut.complete()
|
fut.complete()
|
||||||
return fut
|
return fut
|
||||||
|
|
|
@ -249,7 +249,7 @@ proc processPacket[A](r: UtpRouter[A], p: Packet, sender: A) {.async.}=
|
||||||
else:
|
else:
|
||||||
# TODO: add keeping track of recently send reset packets and do not send
|
# TODO: add keeping track of recently send reset packets and do not send
|
||||||
# reset to peers which we recently send reset to.
|
# reset to peers which we recently send reset to.
|
||||||
debug "Received FIN/DATA/ACK on not known socket sending reset"
|
debug "Received FIN/DATA/ACK on unknown socket, sending reset"
|
||||||
let rstPacket = resetPacket(
|
let rstPacket = resetPacket(
|
||||||
randUint16(r.rng[]), p.header.connectionId, p.header.seqNr)
|
randUint16(r.rng[]), p.header.connectionId, p.header.seqNr)
|
||||||
await r.sendCb(sender, encodePacket(rstPacket))
|
await r.sendCb(sender, encodePacket(rstPacket))
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[sugar, deques],
|
std/[sugar, deques],
|
||||||
chronos, chronicles,
|
chronos, chronicles, metrics,
|
||||||
stew/[results, bitops2],
|
stew/[results, bitops2],
|
||||||
./growable_buffer,
|
./growable_buffer,
|
||||||
./packets,
|
./packets,
|
||||||
|
@ -203,7 +203,7 @@ type
|
||||||
# peer
|
# peer
|
||||||
rto: Duration
|
rto: Duration
|
||||||
|
|
||||||
# RTO timeout will happen when currenTime > rtoTimeout
|
# RTO timeout happens when currentTime > rtoTimeout
|
||||||
rtoTimeout: Moment
|
rtoTimeout: Moment
|
||||||
|
|
||||||
# rcvBuffer
|
# rcvBuffer
|
||||||
|
@ -215,7 +215,7 @@ type
|
||||||
# readers waiting for data
|
# readers waiting for data
|
||||||
pendingReads: Deque[ReadReq]
|
pendingReads: Deque[ReadReq]
|
||||||
|
|
||||||
# loop called every 500ms to check for on going timeout status
|
# loop called every 500ms to check for timeouts
|
||||||
checkTimeoutsLoop: Future[void]
|
checkTimeoutsLoop: Future[void]
|
||||||
|
|
||||||
# number on consecutive re-transmissions
|
# number on consecutive re-transmissions
|
||||||
|
@ -257,15 +257,15 @@ type
|
||||||
# timer which is started when peer max window drops below current packet size
|
# timer which is started when peer max window drops below current packet size
|
||||||
zeroWindowTimer: Option[Moment]
|
zeroWindowTimer: Option[Moment]
|
||||||
|
|
||||||
# last measured delay between current local timestamp, and remote sent
|
# last measured delay between current local timestamp and remote sent
|
||||||
# timestamp. In microseconds
|
# timestamp, in microseconds.
|
||||||
replayMicro: uint32
|
replayMicro: uint32
|
||||||
|
|
||||||
# indicator if we're in slow-start (exponential growth) phase
|
# indicator if socket is in in slow-start (exponential growth) phase
|
||||||
slowStart: bool
|
slowStart: bool
|
||||||
|
|
||||||
# indicator if we're in fast time out mode i.e we will resend
|
# indicator if socket is in fast time-out mode, i.e will resend oldest
|
||||||
# oldest packet un-acked in case of newer packet arriving
|
# not ACK'ed packet when newer packet is received.
|
||||||
fastTimeout: bool
|
fastTimeout: bool
|
||||||
|
|
||||||
# Sequence number of the next packet we are allowed to fast-resend. This is
|
# Sequence number of the next packet we are allowed to fast-resend. This is
|
||||||
|
@ -278,7 +278,7 @@ type
|
||||||
# counter of duplicate acks
|
# counter of duplicate acks
|
||||||
duplicateAck: uint16
|
duplicateAck: uint16
|
||||||
|
|
||||||
#the slow-start threshold, in bytes
|
# the slow-start threshold, in bytes
|
||||||
slowStartThreshold: uint32
|
slowStartThreshold: uint32
|
||||||
|
|
||||||
# history of our delays
|
# history of our delays
|
||||||
|
@ -295,8 +295,8 @@ type
|
||||||
|
|
||||||
send: SendCallback[A]
|
send: SendCallback[A]
|
||||||
|
|
||||||
# User driven call back to be called whenever socket is permanently closed i.e
|
# User driven callback to be called whenever socket is permanently closed,
|
||||||
# reaches destroy state
|
# i.e reaches the destroy state
|
||||||
SocketCloseCallback* = proc (): void {.gcsafe, raises: [].}
|
SocketCloseCallback* = proc (): void {.gcsafe, raises: [].}
|
||||||
|
|
||||||
ConnectionError* = object of CatchableError
|
ConnectionError* = object of CatchableError
|
||||||
|
@ -543,13 +543,14 @@ proc checkTimeouts(socket: UtpSocket) =
|
||||||
if socket.isOpened():
|
if socket.isOpened():
|
||||||
let currentPacketSize = socket.getPacketSize()
|
let currentPacketSize = socket.getPacketSize()
|
||||||
|
|
||||||
if (socket.zeroWindowTimer.isSome() and currentTime > socket.zeroWindowTimer.unsafeGet()):
|
if (socket.zeroWindowTimer.isSome() and
|
||||||
|
currentTime > socket.zeroWindowTimer.unsafeGet()):
|
||||||
if socket.maxRemoteWindow <= currentPacketSize:
|
if socket.maxRemoteWindow <= currentPacketSize:
|
||||||
# Reset remote window, to minimal value which will fit at least two packet
|
# Reset maxRemoteWindow to minimal value which will fit at least two
|
||||||
let minimalRemoteWindow = 2 * socket.socketConfig.payloadSize
|
# packets
|
||||||
socket.maxRemoteWindow = minimalRemoteWindow
|
let remoteWindow = 2 * socket.socketConfig.payloadSize
|
||||||
debug "Reset remote window to minimal value",
|
socket.maxRemoteWindow = remoteWindow
|
||||||
minRemote = minimalRemoteWindow
|
debug "Reset remote window to minimal value", remoteWindow
|
||||||
socket.zeroWindowTimer = none[Moment]()
|
socket.zeroWindowTimer = none[Moment]()
|
||||||
|
|
||||||
if (currentTime > socket.rtoTimeout):
|
if (currentTime > socket.rtoTimeout):
|
||||||
|
@ -560,12 +561,14 @@ proc checkTimeouts(socket: UtpSocket) =
|
||||||
curWindowPackets = socket.curWindowPackets,
|
curWindowPackets = socket.curWindowPackets,
|
||||||
curWindowBytes = socket.currentWindow
|
curWindowBytes = socket.currentWindow
|
||||||
|
|
||||||
# TODO add handling of probe time outs. Reference implementation has mechanism
|
# TODO:
|
||||||
# of sending probes to determine mtu size. Probe timeouts do not count to standard
|
# Add handling of probing on timeouts. The reference implementation has
|
||||||
# timeouts calculations
|
# a mechanism of sending probes to determine MTU size. Probe timeouts are
|
||||||
|
# not taking into account for the timeout calculation.
|
||||||
|
|
||||||
# client initiated connections, but did not send following data packet in rto
|
# For client initiated connections: SYN received but did not receive
|
||||||
# time and our socket is configured to start in SynRecv state.
|
# following data packet in rto time and the socket is configured to start
|
||||||
|
# in SynRecv state (to avoid amplifcation by IP spoofing).
|
||||||
if (socket.state == SynRecv):
|
if (socket.state == SynRecv):
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
return
|
return
|
||||||
|
@ -589,13 +592,13 @@ proc checkTimeouts(socket: UtpSocket) =
|
||||||
socket.retransmitTimeout = newTimeout
|
socket.retransmitTimeout = newTimeout
|
||||||
socket.rtoTimeout = currentTime + newTimeout
|
socket.rtoTimeout = currentTime + newTimeout
|
||||||
|
|
||||||
# on timeout reset duplicate ack counter
|
# on timeout, reset the duplicate ack counter
|
||||||
socket.duplicateAck = 0
|
socket.duplicateAck = 0
|
||||||
|
|
||||||
if (socket.curWindowPackets == 0 and socket.maxWindow > currentPacketSize):
|
if (socket.curWindowPackets == 0 and socket.maxWindow > currentPacketSize):
|
||||||
# there are no packets in flight even though there is place for more than whole packet
|
# There are no packets in flight even though there is space for more
|
||||||
# this means connection is just idling. Reset window by 1/3'rd but no more
|
# than a full packet. This means the connection is just idling.
|
||||||
# than to fit at least one packet.
|
# Reset window by 1/3'rd but no more than to fit at least one packet.
|
||||||
let oldMaxWindow = socket.maxWindow
|
let oldMaxWindow = socket.maxWindow
|
||||||
let newMaxWindow = max((oldMaxWindow * 2) div 3, currentPacketSize)
|
let newMaxWindow = max((oldMaxWindow * 2) div 3, currentPacketSize)
|
||||||
|
|
||||||
|
@ -617,14 +620,15 @@ proc checkTimeouts(socket: UtpSocket) =
|
||||||
socket.maxWindow = currentPacketSize
|
socket.maxWindow = currentPacketSize
|
||||||
socket.slowStart = true
|
socket.slowStart = true
|
||||||
|
|
||||||
# This will have much more sense when we will add handling of selective acks
|
# Note: with selective acks enabled, every selectively acked packet resets
|
||||||
# as then every selectively acked packet resets timeout timer and removes packet
|
# the timeout timer and removes te packet from the outBuffer.
|
||||||
# from out buffer.
|
|
||||||
markAllPacketAsLost(socket)
|
markAllPacketAsLost(socket)
|
||||||
|
|
||||||
let oldestPacketSeqNr = socket.seqNr - socket.curWindowPackets
|
let oldestPacketSeqNr = socket.seqNr - socket.curWindowPackets
|
||||||
# resend oldest packet if there are some packets in flight, and oldestpacket was already sent
|
# resend the oldest packet if there are some packets in flight and the
|
||||||
if (socket.curWindowPackets > 0 and socket.outBuffer[oldestPacketSeqNr].transmissions > 0):
|
# oldest packet was already sent
|
||||||
|
if (socket.curWindowPackets > 0 and
|
||||||
|
socket.outBuffer[oldestPacketSeqNr].transmissions > 0):
|
||||||
inc socket.retransmitCount
|
inc socket.retransmitCount
|
||||||
socket.fastTimeout = true
|
socket.fastTimeout = true
|
||||||
|
|
||||||
|
@ -633,11 +637,10 @@ proc checkTimeouts(socket: UtpSocket) =
|
||||||
retransmitCount = socket.retransmitCount,
|
retransmitCount = socket.retransmitCount,
|
||||||
curWindowPackets = socket.curWindowPackets
|
curWindowPackets = socket.curWindowPackets
|
||||||
|
|
||||||
# Oldest packet should always be present, so it is safe to call force
|
# Oldest packet should always be present, so it is safe to force resend
|
||||||
# resend
|
|
||||||
socket.sendPacket(oldestPacketSeqNr)
|
socket.sendPacket(oldestPacketSeqNr)
|
||||||
|
|
||||||
# TODO add sending keep alives when necessary
|
# TODO: add sending keep alives when necessary
|
||||||
|
|
||||||
proc checkTimeoutsLoop(s: UtpSocket) {.async.} =
|
proc checkTimeoutsLoop(s: UtpSocket) {.async.} =
|
||||||
## Loop that check timeouts in the socket.
|
## Loop that check timeouts in the socket.
|
||||||
|
@ -646,8 +649,8 @@ proc checkTimeoutsLoop(s: UtpSocket) {.async.} =
|
||||||
await sleepAsync(checkTimeoutsLoopInterval)
|
await sleepAsync(checkTimeoutsLoopInterval)
|
||||||
s.eventQueue.putNoWait(SocketEvent(kind: CheckTimeouts))
|
s.eventQueue.putNoWait(SocketEvent(kind: CheckTimeouts))
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
# check timeouts loop is last running future managed by socket, if its
|
# checkTimeoutsLoop is the last running future managed by the socket, when
|
||||||
# cancelled we can fire closeEvent
|
# it's cancelled the closeEvent can be fired.
|
||||||
s.closeEvent.fire()
|
s.closeEvent.fire()
|
||||||
trace "checkTimeoutsLoop canceled"
|
trace "checkTimeoutsLoop canceled"
|
||||||
raise exc
|
raise exc
|
||||||
|
@ -721,12 +724,13 @@ proc isClosed*(socket: UtpSocket): bool =
|
||||||
|
|
||||||
proc isClosedAndCleanedUpAllResources*(socket: UtpSocket): bool =
|
proc isClosedAndCleanedUpAllResources*(socket: UtpSocket): bool =
|
||||||
## Test Api to check that all resources are properly cleaned up
|
## Test Api to check that all resources are properly cleaned up
|
||||||
socket.isClosed() and socket.eventLoop.cancelled() and socket.checkTimeoutsLoop.cancelled()
|
socket.isClosed() and socket.eventLoop.cancelled() and
|
||||||
|
socket.checkTimeoutsLoop.cancelled()
|
||||||
|
|
||||||
proc destroy*(s: UtpSocket) =
|
proc destroy*(s: UtpSocket) =
|
||||||
debug "Destroying socket", to = s.socketKey
|
debug "Destroying socket", to = s.socketKey
|
||||||
## Moves socket to destroy state and clean all resources.
|
## Moves socket to destroy state and clean all resources.
|
||||||
## Remote is not notified in any way about socket end of life
|
## Remote is not notified in any way about socket end of life.
|
||||||
s.state = Destroy
|
s.state = Destroy
|
||||||
s.eventLoop.cancel()
|
s.eventLoop.cancel()
|
||||||
# This procedure initiate cleanup process which goes like:
|
# This procedure initiate cleanup process which goes like:
|
||||||
|
@ -737,9 +741,9 @@ proc destroy*(s: UtpSocket) =
|
||||||
# future shows as cancelled, but handler for CancelledError is not run
|
# future shows as cancelled, but handler for CancelledError is not run
|
||||||
|
|
||||||
proc destroyWait*(s: UtpSocket) {.async.} =
|
proc destroyWait*(s: UtpSocket) {.async.} =
|
||||||
## Moves socket to destroy state and clean all reasources and wait for all registered
|
## Moves socket to destroy state and clean all resources and wait for all
|
||||||
## callback to fire
|
## registered callbacks to fire,
|
||||||
## Remote is not notified in any way about socket end of life
|
## Remote is not notified in any way about socket end of life.
|
||||||
s.destroy()
|
s.destroy()
|
||||||
await s.closeEvent.wait()
|
await s.closeEvent.wait()
|
||||||
await allFutures(s.closeCallbacks)
|
await allFutures(s.closeCallbacks)
|
||||||
|
@ -802,9 +806,10 @@ proc ackPacket(socket: UtpSocket, seqNr: uint16, currentTime: Moment): AckResult
|
||||||
pkTransmissions = packet.transmissions,
|
pkTransmissions = packet.transmissions,
|
||||||
pkNeedResend = packet.needResend
|
pkNeedResend = packet.needResend
|
||||||
|
|
||||||
# from spec: The rtt and rtt_var is only updated for packets that were sent only once.
|
# from spec: The rtt and rtt_var is only updated for packets that were sent
|
||||||
# This avoids problems with figuring out which packet was acked, the first or the second one.
|
# only once. This avoids the problem of figuring out which packet was acked,
|
||||||
# it is standard solution to retransmission ambiguity problem
|
# the first or the second one. It is standard solution to the retransmission
|
||||||
|
# ambiguity problem.
|
||||||
if packet.transmissions == 1:
|
if packet.transmissions == 1:
|
||||||
socket.updateTimeouts(packet.timeSent, currentTime)
|
socket.updateTimeouts(packet.timeSent, currentTime)
|
||||||
|
|
||||||
|
@ -815,10 +820,11 @@ proc ackPacket(socket: UtpSocket, seqNr: uint16, currentTime: Moment): AckResult
|
||||||
# been considered timed-out, and is not included in
|
# been considered timed-out, and is not included in
|
||||||
# the cur_window anymore
|
# the cur_window anymore
|
||||||
if (not packet.needResend):
|
if (not packet.needResend):
|
||||||
doAssert(socket.currentWindow >= packet.payloadLength, "Window should always be larger than packet length")
|
doAssert(socket.currentWindow >= packet.payloadLength,
|
||||||
|
"Window should always be larger than packet length")
|
||||||
socket.currentWindow = socket.currentWindow - packet.payloadLength
|
socket.currentWindow = socket.currentWindow - packet.payloadLength
|
||||||
|
|
||||||
# we removed packet from our out going buffer
|
# recalculate as packet was removed from the outgoing buffer
|
||||||
socket.outBufferBytes = socket.outBufferBytes - packet.payloadLength
|
socket.outBufferBytes = socket.outBufferBytes - packet.payloadLength
|
||||||
|
|
||||||
socket.retransmitCount = 0
|
socket.retransmitCount = 0
|
||||||
|
@ -832,7 +838,8 @@ proc ackPackets(socket: UtpSocket, nrPacketsToAck: uint16, currentTime: Moment)
|
||||||
## Ack packets in outgoing buffer based on ack number in the received packet
|
## Ack packets in outgoing buffer based on ack number in the received packet
|
||||||
var i = 0
|
var i = 0
|
||||||
while i < int(nrPacketsToAck):
|
while i < int(nrPacketsToAck):
|
||||||
let result = socket.ackPacket(socket.seqNr - socket.curWindowPackets, currentTime)
|
let result = socket.ackPacket(
|
||||||
|
socket.seqNr - socket.curWindowPackets, currentTime)
|
||||||
case result
|
case result
|
||||||
of PacketAcked:
|
of PacketAcked:
|
||||||
dec socket.curWindowPackets
|
dec socket.curWindowPackets
|
||||||
|
@ -844,7 +851,9 @@ proc ackPackets(socket: UtpSocket, nrPacketsToAck: uint16, currentTime: Moment)
|
||||||
|
|
||||||
inc i
|
inc i
|
||||||
|
|
||||||
proc calculateAckedbytes(socket: UtpSocket, nrPacketsToAck: uint16, now: Moment): (uint32, Duration) =
|
proc calculateAckedbytes(
|
||||||
|
socket: UtpSocket, nrPacketsToAck: uint16, now: Moment):
|
||||||
|
(uint32, Duration) =
|
||||||
var i: uint16 = 0
|
var i: uint16 = 0
|
||||||
var ackedBytes: uint32 = 0
|
var ackedBytes: uint32 = 0
|
||||||
var minRtt: Duration = InfiniteDuration
|
var minRtt: Duration = InfiniteDuration
|
||||||
|
@ -891,9 +900,11 @@ proc isAckNrInvalid(socket: UtpSocket, packet: Packet): bool =
|
||||||
)
|
)
|
||||||
|
|
||||||
# counts the number of bytes acked by selective ack header
|
# counts the number of bytes acked by selective ack header
|
||||||
proc calculateSelectiveAckBytes*(socket: UtpSocket, receivedPackedAckNr: uint16, ext: SelectiveAckExtension): uint32 =
|
proc calculateSelectiveAckBytes*(
|
||||||
# we add 2, as the first bit in the mask therefore represents ackNr + 2 because
|
socket: UtpSocket, receivedPackedAckNr: uint16, ext: SelectiveAckExtension):
|
||||||
# ackNr + 1 (i.e next expected packet) is considered lost.
|
uint32 =
|
||||||
|
# Add 2, as the first bit in the mask represents ackNr + 2 because ackNr + 1
|
||||||
|
# (i.e next expected packet) is considered lost.
|
||||||
let base = receivedPackedAckNr + 2
|
let base = receivedPackedAckNr + 2
|
||||||
|
|
||||||
if socket.curWindowPackets == 0:
|
if socket.curWindowPackets == 0:
|
||||||
|
@ -925,12 +936,13 @@ proc calculateSelectiveAckBytes*(socket: UtpSocket, receivedPackedAckNr: uint16
|
||||||
|
|
||||||
return ackedBytes
|
return ackedBytes
|
||||||
|
|
||||||
# decays maxWindow size by half if time is right i.e it is at least 100m since last
|
# decays maxWindow size by half if time is right i.e it is at least 100m since
|
||||||
# window decay
|
# last window decay
|
||||||
proc tryDecayWindow(socket: UtpSocket, now: Moment) =
|
proc tryDecayWindow(socket: UtpSocket, now: Moment) =
|
||||||
if (now - socket.lastWindowDecay >= maxWindowDecay):
|
if (now - socket.lastWindowDecay >= maxWindowDecay):
|
||||||
socket.lastWindowDecay = now
|
socket.lastWindowDecay = now
|
||||||
let newMaxWindow = max(uint32(0.5 * float64(socket.maxWindow)), uint32(minWindowSize))
|
let newMaxWindow =
|
||||||
|
max(uint32(0.5 * float64(socket.maxWindow)), uint32(minWindowSize))
|
||||||
|
|
||||||
debug "Decaying maxWindow",
|
debug "Decaying maxWindow",
|
||||||
oldWindow = socket.maxWindow,
|
oldWindow = socket.maxWindow,
|
||||||
|
@ -940,10 +952,13 @@ proc tryDecayWindow(socket: UtpSocket, now: Moment) =
|
||||||
socket.slowStart = false
|
socket.slowStart = false
|
||||||
socket.slowStartThreshold = newMaxWindow
|
socket.slowStartThreshold = newMaxWindow
|
||||||
|
|
||||||
# ack packets (removes them from out going buffer) based on selective ack extension header
|
# ack packets (removes them from out going buffer) based on selective ack
|
||||||
proc selectiveAckPackets(socket: UtpSocket, receivedPackedAckNr: uint16, ext: SelectiveAckExtension, currentTime: Moment): void =
|
# extension header
|
||||||
# we add 2, as the first bit in the mask therefore represents ackNr + 2 because
|
proc selectiveAckPackets(
|
||||||
# ackNr + 1 (i.e next expected packet) is considered lost.
|
socket: UtpSocket, receivedPackedAckNr: uint16, ext: SelectiveAckExtension,
|
||||||
|
currentTime: Moment): void =
|
||||||
|
# Add 2, as the first bit in the mask represents ackNr + 2 because ackNr + 1
|
||||||
|
# (i.e next expected packet) is considered lost.
|
||||||
let base = receivedPackedAckNr + 2
|
let base = receivedPackedAckNr + 2
|
||||||
|
|
||||||
if socket.curWindowPackets == 0:
|
if socket.curWindowPackets == 0:
|
||||||
|
@ -951,9 +966,10 @@ proc selectiveAckPackets(socket: UtpSocket, receivedPackedAckNr: uint16, ext: S
|
||||||
|
|
||||||
var bits = (len(ext.acks)) * 8 - 1
|
var bits = (len(ext.acks)) * 8 - 1
|
||||||
|
|
||||||
# number of packets acked by this selective acks, it also works as duplicate ack
|
# number of packets acked by this selective ack, it also works as duplicate
|
||||||
# counter.
|
# ack counter.
|
||||||
# from spec: Each packet that is acked in the selective ack message counts as one duplicate ack
|
# from spec: Each packet that is acked in the selective ack message counts as
|
||||||
|
# one duplicate ack
|
||||||
var counter = 0
|
var counter = 0
|
||||||
|
|
||||||
# sequence numbers of packets which should be resend
|
# sequence numbers of packets which should be resend
|
||||||
|
@ -986,7 +1002,8 @@ proc selectiveAckPackets(socket: UtpSocket, receivedPackedAckNr: uint16, ext: S
|
||||||
dec bits
|
dec bits
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if counter >= duplicateAcksBeforeResend and (v - socket.fastResendSeqNr) <= reorderBufferMaxSize:
|
if counter >= duplicateAcksBeforeResend and
|
||||||
|
(v - socket.fastResendSeqNr) <= reorderBufferMaxSize:
|
||||||
debug "No ack for packet",
|
debug "No ack for packet",
|
||||||
pkAckNr = v,
|
pkAckNr = v,
|
||||||
dupAckCounter = counter,
|
dupAckCounter = counter,
|
||||||
|
@ -995,15 +1012,16 @@ proc selectiveAckPackets(socket: UtpSocket, receivedPackedAckNr: uint16, ext: S
|
||||||
|
|
||||||
dec bits
|
dec bits
|
||||||
|
|
||||||
let nextExpectedPacketSeqNr = base - 1'u16
|
# When resending packets, the first packet should be the first unacked packet,
|
||||||
# if we are about to start to resending first packet should be the first unacked packet
|
|
||||||
# ie. base - 1
|
# ie. base - 1
|
||||||
if counter >= duplicateAcksBeforeResend and (nextExpectedPacketSeqNr - socket.fastResendSeqNr) <= reorderBufferMaxSize:
|
let nextExpectedPacketSeqNr = base - 1'u16
|
||||||
debug "No ack for packet",
|
if counter >= duplicateAcksBeforeResend and
|
||||||
pkAckNr = nextExpectedPacketSeqNr,
|
(nextExpectedPacketSeqNr - socket.fastResendSeqNr) <= reorderBufferMaxSize:
|
||||||
dupAckCounter = counter,
|
debug "No ack for packet",
|
||||||
fastResSeqNr = socket.fastResendSeqNr
|
pkAckNr = nextExpectedPacketSeqNr,
|
||||||
resends.add(nextExpectedPacketSeqNr)
|
dupAckCounter = counter,
|
||||||
|
fastResSeqNr = socket.fastResendSeqNr
|
||||||
|
resends.add(nextExpectedPacketSeqNr)
|
||||||
|
|
||||||
var i = high(resends)
|
var i = high(resends)
|
||||||
var registerLoss: bool = false
|
var registerLoss: bool = false
|
||||||
|
@ -1043,12 +1061,14 @@ proc selectiveAckPackets(socket: UtpSocket, receivedPackedAckNr: uint16, ext: S
|
||||||
socket.duplicateAck = uint16(counter)
|
socket.duplicateAck = uint16(counter)
|
||||||
|
|
||||||
# Public mainly for test purposes
|
# Public mainly for test purposes
|
||||||
# generates bit mask which indicates which packets are already in socket
|
# Generates bit mask which indicates which packets are already in socket
|
||||||
# reorder buffer
|
# reorder buffer.
|
||||||
# from speck:
|
# From spec:
|
||||||
# The bitmask has reverse byte order. The first byte represents packets [ack_nr + 2, ack_nr + 2 + 7] in reverse order
|
# The bitmask has reverse byte order. The first byte represents packets
|
||||||
# The least significant bit in the byte represents ack_nr + 2, the most significant bit in the byte represents ack_nr + 2 + 7
|
# [ack_nr + 2, ack_nr + 2 + 7] in reverse order.
|
||||||
# The next byte in the mask represents [ack_nr + 2 + 8, ack_nr + 2 + 15] in reverse order, and so on
|
# The least significant bit in the byte represents ack_nr + 2, the most
|
||||||
|
# significant bit in the byte represents ack_nr + 2 + 7. The next byte in the
|
||||||
|
# mask represents [ack_nr + 2 + 8, ack_nr + 2 + 15] in reverse order, and so on.
|
||||||
proc generateSelectiveAckBitMask*(socket: UtpSocket): array[4, byte] =
|
proc generateSelectiveAckBitMask*(socket: UtpSocket): array[4, byte] =
|
||||||
let window = min(32, socket.inBuffer.len())
|
let window = min(32, socket.inBuffer.len())
|
||||||
var arr: array[4, uint8] = [0'u8, 0, 0, 0]
|
var arr: array[4, uint8] = [0'u8, 0, 0, 0]
|
||||||
|
@ -1181,10 +1201,11 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
duplicateAckCounter = socket.duplicateAck
|
duplicateAckCounter = socket.duplicateAck
|
||||||
else:
|
else:
|
||||||
socket.duplicateAck = 0
|
socket.duplicateAck = 0
|
||||||
# spec says that in case of duplicate ack counter larger that duplicateAcksBeforeResend
|
# Spec states that in case of a duplicate ack counter larger than
|
||||||
# we should re-send oldest packet, on the other hand reference implementation
|
# `duplicateAcksBeforeResend` the oldest packet should be resend. However, the
|
||||||
# has code path which does it commented out with todo. Currently to be as close
|
# reference implementation has the code path which does this commented out
|
||||||
# to reference impl we do not resend packets in that case
|
# with a todo. Currently the reference implementation is follow and packets
|
||||||
|
# are not resend in this case.
|
||||||
|
|
||||||
debug "Packet state variables",
|
debug "Packet state variables",
|
||||||
pastExpected = pastExpected,
|
pastExpected = pastExpected,
|
||||||
|
@ -1192,15 +1213,16 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
|
|
||||||
# If packet is totally off the mark, short-circuit the processing
|
# If packet is totally off the mark, short-circuit the processing
|
||||||
if pastExpected >= reorderBufferMaxSize:
|
if pastExpected >= reorderBufferMaxSize:
|
||||||
|
# if `pastExpected` is a really big number (for example: uint16.high) then
|
||||||
# if `pastExpected` is really big number (for example: uint16.high) then most
|
# most probably we are receiving packet which we already received.
|
||||||
# probably we are receiving packet which we already received
|
# example: socket already received packet with `seqNr = 10` so the
|
||||||
# example: we already received packet with `seqNr = 10` so our `socket.ackNr = 10`
|
# `socket.ackNr = 10`.
|
||||||
# if we receive this packet once again then `pastExpected = 10 - 10 - 1` which
|
# Then when this packet is received once again then
|
||||||
# equals (due to wrapping) 65535
|
# `pastExpected = 10 - 10 - 1` which equals (due to wrapping) 65535.
|
||||||
# this means that remote most probably did not receive our ack, so we need to resend
|
# This means that remote most probably did not receive our ack, so we need
|
||||||
# it. We are doing it for last `reorderBufferMaxSize` packets
|
# to resend it. We are doing it for last `reorderBufferMaxSize` packets.
|
||||||
let isPossibleDuplicatedOldPacket = pastExpected >= (int(uint16.high) + 1) - reorderBufferMaxSize
|
let isPossibleDuplicatedOldPacket =
|
||||||
|
pastExpected >= (int(uint16.high) + 1) - reorderBufferMaxSize
|
||||||
|
|
||||||
if (isPossibleDuplicatedOldPacket and p.header.pType != ST_STATE):
|
if (isPossibleDuplicatedOldPacket and p.header.pType != ST_STATE):
|
||||||
socket.sendAck()
|
socket.sendAck()
|
||||||
|
@ -1209,13 +1231,15 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
pastExpected = pastExpected
|
pastExpected = pastExpected
|
||||||
return
|
return
|
||||||
|
|
||||||
var (ackedBytes, minRtt) = socket.calculateAckedbytes(acks, timestampInfo.moment)
|
var (ackedBytes, minRtt) =
|
||||||
|
socket.calculateAckedbytes(acks, timestampInfo.moment)
|
||||||
|
|
||||||
debug "Bytes acked by classic ack",
|
debug "Bytes acked by classic ack",
|
||||||
bytesAcked = ackedBytes
|
bytesAcked = ackedBytes
|
||||||
|
|
||||||
if (p.eack.isSome()):
|
if (p.eack.isSome()):
|
||||||
let selectiveAckedBytes = socket.calculateSelectiveAckBytes(pkAckNr, p.eack.unsafeGet())
|
let selectiveAckedBytes =
|
||||||
|
socket.calculateSelectiveAckBytes(pkAckNr, p.eack.unsafeGet())
|
||||||
debug "Bytes acked by selective ack",
|
debug "Bytes acked by selective ack",
|
||||||
bytesAcked = selectiveAckedBytes
|
bytesAcked = selectiveAckedBytes
|
||||||
ackedBytes = ackedBytes + selectiveAckedBytes
|
ackedBytes = ackedBytes + selectiveAckedBytes
|
||||||
|
@ -1225,8 +1249,8 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
# we are using uint32 not a Duration, to wrap a round in case of
|
# we are using uint32 not a Duration, to wrap a round in case of
|
||||||
# sentTimeRemote > receipTimestamp. This can happen as local and remote
|
# sentTimeRemote > receipTimestamp. This can happen as local and remote
|
||||||
# clock can be not synchronized or even using different system clock.
|
# clock can be not synchronized or even using different system clock.
|
||||||
# i.e this number itself does not tell anything and is only used to feedback it
|
# i.e this number itself does not tell anything and is only used to feedback
|
||||||
# to remote peer with each sent packet
|
# it to remote peer with each sent packet
|
||||||
let remoteDelay =
|
let remoteDelay =
|
||||||
if (sentTimeRemote == 0):
|
if (sentTimeRemote == 0):
|
||||||
0'u32
|
0'u32
|
||||||
|
@ -1246,7 +1270,8 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
if (prevRemoteDelayBase != 0 and
|
if (prevRemoteDelayBase != 0 and
|
||||||
wrapCompareLess(socket.remoteHistogram.delayBase, prevRemoteDelayBase) and
|
wrapCompareLess(socket.remoteHistogram.delayBase, prevRemoteDelayBase) and
|
||||||
prevRemoteDelayBase - socket.remoteHistogram.delayBase <= 10000'u32):
|
prevRemoteDelayBase - socket.remoteHistogram.delayBase <= 10000'u32):
|
||||||
socket.ourHistogram.shift(prevRemoteDelayBase - socket.remoteHistogram.delayBase)
|
socket.ourHistogram.shift(
|
||||||
|
prevRemoteDelayBase - socket.remoteHistogram.delayBase)
|
||||||
|
|
||||||
let actualDelay = p.header.timestampDiff
|
let actualDelay = p.header.timestampDiff
|
||||||
|
|
||||||
|
@ -1286,10 +1311,12 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
slowStartThreshold = newSlowStartThreshold,
|
slowStartThreshold = newSlowStartThreshold,
|
||||||
slowstart = newSlowStart
|
slowstart = newSlowStart
|
||||||
|
|
||||||
if (socket.zeroWindowTimer.isNone() and socket.maxRemoteWindow <= currentPacketSize):
|
if (socket.zeroWindowTimer.isNone() and
|
||||||
# when zeroWindowTimer will be hit and maxRemoteWindow still will be equal to 0
|
socket.maxRemoteWindow <= currentPacketSize):
|
||||||
# then it will be reset to minimal value
|
# when zeroWindowTimer is hit and maxRemoteWindow still is equal
|
||||||
socket.zeroWindowTimer = some(timestampInfo.moment + socket.socketConfig.remoteWindowResetTimeout)
|
# to 0 then it will be reset to the minimal value
|
||||||
|
socket.zeroWindowTimer =
|
||||||
|
some(timestampInfo.moment + socket.socketConfig.remoteWindowResetTimeout)
|
||||||
|
|
||||||
debug "Remote window size dropped below packet size",
|
debug "Remote window size dropped below packet size",
|
||||||
currentTime = timestampInfo.moment,
|
currentTime = timestampInfo.moment,
|
||||||
|
@ -1298,15 +1325,16 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
|
|
||||||
socket.tryfinalizeConnection(p)
|
socket.tryfinalizeConnection(p)
|
||||||
|
|
||||||
# socket.curWindowPackets == acks means that this packet acked all remaining packets
|
# socket.curWindowPackets == acks means that this packet acked all remaining
|
||||||
# including the sent fin packets
|
# packets including the sent FIN packets
|
||||||
if (socket.finSent and socket.curWindowPackets == acks):
|
if (socket.finSent and socket.curWindowPackets == acks):
|
||||||
debug "FIN acked, destroying socket"
|
debug "FIN acked, destroying socket"
|
||||||
socket.finAcked = true
|
socket.finAcked = true
|
||||||
# this bit of utp spec is a bit under specified (i.e there is not specification at all)
|
# this part of the uTP spec is a bit under specified, i.e there is no
|
||||||
# reference implementation moves socket to destroy state in case that our fin was acked
|
# specification at all. The reference implementation moves socket to destroy
|
||||||
# and socket is considered closed for reading and writing.
|
# state in case that our FIN was acked and socket is considered closed for
|
||||||
# but in theory remote could stil write some data on this socket (or even its own fin)
|
# reading and writing. But in theory, the remote could still write some data
|
||||||
|
# on this socket (or even its own FIN).
|
||||||
socket.destroy()
|
socket.destroy()
|
||||||
|
|
||||||
# Update fast resend counter to avoid resending old packet twice
|
# Update fast resend counter to avoid resending old packet twice
|
||||||
|
@ -1336,11 +1364,12 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
|
|
||||||
socket.ackPackets(acks, timestampInfo.moment)
|
socket.ackPackets(acks, timestampInfo.moment)
|
||||||
|
|
||||||
# packets in front may have been acked by selective ack, decrease window until we hit
|
# packets in front may have been acked by selective ack, decrease window until
|
||||||
# a packet that is still waiting to be acked
|
# we hit a packet that is still waiting to be acked.
|
||||||
while (socket.curWindowPackets > 0 and socket.outBuffer.get(socket.seqNr - socket.curWindowPackets).isNone()):
|
while (socket.curWindowPackets > 0 and
|
||||||
|
socket.outBuffer.get(socket.seqNr - socket.curWindowPackets).isNone()):
|
||||||
dec socket.curWindowPackets
|
dec socket.curWindowPackets
|
||||||
debug "Packet in front hase been acked by selective ack. Decrese window",
|
debug "Packet in front has been acked by selective ack. Decrease window",
|
||||||
windowPackets = socket.curWindowPackets
|
windowPackets = socket.curWindowPackets
|
||||||
|
|
||||||
# fast timeout
|
# fast timeout
|
||||||
|
@ -1354,19 +1383,20 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
|
|
||||||
|
|
||||||
if oldestOutstandingPktSeqNr != socket.fastResendSeqNr:
|
if oldestOutstandingPktSeqNr != socket.fastResendSeqNr:
|
||||||
# fastResendSeqNr do not point to oldest unacked packet, we probably already resent
|
# fastResendSeqNr does not point to oldest unacked packet, we probably
|
||||||
# packet that timed-out. Leave fast timeout mode
|
# already resent the packet that timed-out. Leave on fast timeout mode.
|
||||||
socket.fastTimeout = false
|
socket.fastTimeout = false
|
||||||
else:
|
else:
|
||||||
let shouldReSendPacket = socket.outBuffer.exists(oldestOutstandingPktSeqNr, (p: OutgoingPacket) => p.transmissions > 0)
|
let shouldReSendPacket = socket.outBuffer.exists(
|
||||||
|
oldestOutstandingPktSeqNr, (p: OutgoingPacket) => p.transmissions > 0)
|
||||||
if shouldReSendPacket:
|
if shouldReSendPacket:
|
||||||
debug "Packet fast timeout resend",
|
debug "Packet fast timeout resend",
|
||||||
pkSeqNr = oldestOutstandingPktSeqNr
|
pkSeqNr = oldestOutstandingPktSeqNr
|
||||||
|
|
||||||
inc socket.fastResendSeqNr
|
inc socket.fastResendSeqNr
|
||||||
|
|
||||||
# Is is safe to call force resend as we already checked shouldReSendPacket
|
# It is safe to call force resend as we already checked
|
||||||
# condition
|
# `shouldReSendPacket` condition.
|
||||||
socket.sendPacket(oldestOutstandingPktSeqNr)
|
socket.sendPacket(oldestOutstandingPktSeqNr)
|
||||||
|
|
||||||
if (p.eack.isSome()):
|
if (p.eack.isSome()):
|
||||||
|
@ -1387,12 +1417,14 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
let payloadLength = len(p.payload)
|
let payloadLength = len(p.payload)
|
||||||
if (payloadLength > 0 and (not socket.readShutdown)):
|
if (payloadLength > 0 and (not socket.readShutdown)):
|
||||||
# we need to sum both rcv buffer and reorder buffer
|
# we need to sum both rcv buffer and reorder buffer
|
||||||
if (uint32(socket.offset) + socket.inBufferBytes + uint32(payloadLength) > socket.socketConfig.optRcvBuffer):
|
let totalBufferSize =
|
||||||
|
uint32(socket.offset) + socket.inBufferBytes + uint32(payloadLength)
|
||||||
|
if (totalBufferSize > socket.socketConfig.optRcvBuffer):
|
||||||
# even though packet is in order and passes all the checks, it would
|
# even though packet is in order and passes all the checks, it would
|
||||||
# overflow our receive buffer, it means that we are receiving data
|
# overflow our receive buffer, it means that we are receiving data
|
||||||
# faster than we are reading it. Do not ack this packet, and drop received
|
# faster than we are reading it. Do not ack this packet, and drop
|
||||||
# data
|
# received data.
|
||||||
debug "Recevied packet would overflow receive buffer dropping it",
|
debug "Received packet would overflow receive buffer, dropping it",
|
||||||
pkSeqNr = p.header.seqNr,
|
pkSeqNr = p.header.seqNr,
|
||||||
bytesReceived = payloadLength,
|
bytesReceived = payloadLength,
|
||||||
rcvbufferSize = socket.offset,
|
rcvbufferSize = socket.offset,
|
||||||
|
@ -1401,34 +1433,38 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
|
|
||||||
debug "Received data packet",
|
debug "Received data packet",
|
||||||
bytesReceived = payloadLength
|
bytesReceived = payloadLength
|
||||||
# we are getting in order data packet, we can flush data directly to the incoming buffer
|
# we are getting in order data packet, we can flush data directly to the
|
||||||
|
# incoming buffer.
|
||||||
# await upload(addr socket.buffer, unsafeAddr p.payload[0], p.payload.len())
|
# await upload(addr socket.buffer, unsafeAddr p.payload[0], p.payload.len())
|
||||||
moveMem(addr socket.rcvBuffer[socket.offset], unsafeAddr p.payload[0], payloadLength)
|
moveMem(
|
||||||
|
addr socket.rcvBuffer[socket.offset],
|
||||||
|
unsafeAddr p.payload[0], payloadLength)
|
||||||
socket.offset = socket.offset + payloadLength
|
socket.offset = socket.offset + payloadLength
|
||||||
|
|
||||||
# Bytes have been passed to upper layer, we can increase number of last
|
# Bytes have been passed to upper layer, we can increase number of last
|
||||||
# acked packet
|
# acked packet
|
||||||
inc socket.ackNr
|
inc socket.ackNr
|
||||||
|
|
||||||
# check if the following packets are in reorder buffer
|
# check if the following packets are in re-order buffer
|
||||||
|
|
||||||
debug "Looking for packets in re-order buffer",
|
debug "Looking for packets in re-order buffer",
|
||||||
reorderCount = socket.reorderCount
|
reorderCount = socket.reorderCount
|
||||||
|
|
||||||
while true:
|
while true:
|
||||||
# We are doing this in reorder loop, to handle the case when we already received
|
# We are doing this in reorder loop, to handle the case when we already
|
||||||
# fin but there were some gaps before eof
|
# received FIN but there were some gaps before eof.
|
||||||
# we have reached remote eof, and should not receive more packets from remote
|
# we have reached remote eof and should not receive more packets from
|
||||||
if ((not socket.reachedFin) and socket.gotFin and socket.eofPktNr == socket.ackNr):
|
# remote.
|
||||||
|
if ((not socket.reachedFin) and socket.gotFin and
|
||||||
|
socket.eofPktNr == socket.ackNr):
|
||||||
debug "Reached socket EOF"
|
debug "Reached socket EOF"
|
||||||
# In case of reaching eof, it is up to user of library what to to with
|
# In case of reaching eof, it is up to user of library what to do with
|
||||||
# it. With the current implementation, the most appropriate way would be to
|
# it. With the current implementation, the most appropriate way would
|
||||||
# destroy it (as with our implementation we know that remote is destroying its acked fin)
|
# be to destroy it (as with our implementation we know that remote is
|
||||||
# as any other send will either generate timeout, or socket will be forcefully
|
# destroying its acked fin) as any other send will either generate
|
||||||
# closed by reset
|
# timeout, or socket will be forcefully closed by reset
|
||||||
socket.reachedFin = true
|
socket.reachedFin = true
|
||||||
# this is not necessarily true, but as we have already reached eof we can
|
# this is not necessarily true, but as we have already reached eof we
|
||||||
# ignore following packets
|
# can ignore following packets
|
||||||
socket.reorderCount = 0
|
socket.reorderCount = 0
|
||||||
|
|
||||||
if socket.reorderCount == 0:
|
if socket.reorderCount == 0:
|
||||||
|
@ -1454,9 +1490,12 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
rcvbufferSize = socket.offset,
|
rcvbufferSize = socket.offset,
|
||||||
reorderBufferSize = socket.inBufferBytes
|
reorderBufferSize = socket.inBufferBytes
|
||||||
|
|
||||||
# Rcv buffer and reorder buffer are sized that it is always possible to
|
# Rcv buffer and reorder buffer are sized such that it is always
|
||||||
# move data from reorder buffer to rcv buffer without overflow
|
# possible to move data from reorder buffer to rcv buffer without
|
||||||
moveMem(addr socket.rcvBuffer[socket.offset], unsafeAddr packet.payload[0], reorderPacketPayloadLength)
|
# overflow.
|
||||||
|
moveMem(
|
||||||
|
addr socket.rcvBuffer[socket.offset],
|
||||||
|
unsafeAddr packet.payload[0], reorderPacketPayloadLength)
|
||||||
socket.offset = socket.offset + reorderPacketPayloadLength
|
socket.offset = socket.offset + reorderPacketPayloadLength
|
||||||
|
|
||||||
debug "Deleting packet",
|
debug "Deleting packet",
|
||||||
|
@ -1465,7 +1504,8 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
socket.inBuffer.delete(nextPacketNum)
|
socket.inBuffer.delete(nextPacketNum)
|
||||||
inc socket.ackNr
|
inc socket.ackNr
|
||||||
dec socket.reorderCount
|
dec socket.reorderCount
|
||||||
socket.inBufferBytes = socket.inBufferBytes - uint32(reorderPacketPayloadLength)
|
socket.inBufferBytes =
|
||||||
|
socket.inBufferBytes - uint32(reorderPacketPayloadLength)
|
||||||
|
|
||||||
debug "Socket state after processing in order packet",
|
debug "Socket state after processing in order packet",
|
||||||
socketKey = socket.socketKey,
|
socketKey = socket.socketKey,
|
||||||
|
@ -1498,9 +1538,13 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
debug "Packet with seqNr already received",
|
debug "Packet with seqNr already received",
|
||||||
seqNr = pkSeqNr
|
seqNr = pkSeqNr
|
||||||
else:
|
else:
|
||||||
let payloadLength = uint32(len(p.payload))
|
let
|
||||||
if (socket.inBufferBytes + payloadLength <= socket.socketConfig.maxSizeOfReorderBuffer and
|
payloadLength = uint32(len(p.payload))
|
||||||
socket.inBufferBytes + uint32(socket.offset) + payloadLength <= socket.socketConfig.optRcvBuffer):
|
totalReorderSize = socket.inBufferBytes + payloadLength
|
||||||
|
totalBufferSize =
|
||||||
|
socket.inBufferBytes + uint32(socket.offset) + payloadLength
|
||||||
|
if (totalReorderSize <= socket.socketConfig.maxSizeOfReorderBuffer and
|
||||||
|
totalBufferSize <= socket.socketConfig.optRcvBuffer):
|
||||||
|
|
||||||
debug "store packet in reorder buffer",
|
debug "store packet in reorder buffer",
|
||||||
packetBytes = payloadLength,
|
packetBytes = payloadLength,
|
||||||
|
@ -1516,8 +1560,8 @@ proc processPacketInternal(socket: UtpSocket, p: Packet) =
|
||||||
socket.inBufferBytes = socket.inBufferBytes + payloadLength
|
socket.inBufferBytes = socket.inBufferBytes + payloadLength
|
||||||
debug "added out of order packet to reorder buffer",
|
debug "added out of order packet to reorder buffer",
|
||||||
reorderCount = socket.reorderCount
|
reorderCount = socket.reorderCount
|
||||||
# we send ack packet, as we reorder count is > 0, so the eack bitmask will be
|
# we send ack packet, as we reorder count is > 0, so the eack bitmask
|
||||||
# generated
|
# will be generated
|
||||||
socket.sendAck()
|
socket.sendAck()
|
||||||
|
|
||||||
proc processPacket*(socket: UtpSocket, p: Packet): Future[void] =
|
proc processPacket*(socket: UtpSocket, p: Packet): Future[void] =
|
||||||
|
@ -1542,8 +1586,8 @@ proc onRead(socket: UtpSocket, readReq: var ReadReq): ReadResult =
|
||||||
return ReadCancelled
|
return ReadCancelled
|
||||||
|
|
||||||
if socket.atEof():
|
if socket.atEof():
|
||||||
# buffer is already empty and we reached remote fin, just finish read with whatever
|
# buffer is already empty and we reached remote fin, just finish read with
|
||||||
# was already read
|
# whatever was already read
|
||||||
readReq.reader.complete(readReq.bytesAvailable)
|
readReq.reader.complete(readReq.bytesAvailable)
|
||||||
return SocketAlreadyFinished
|
return SocketAlreadyFinished
|
||||||
|
|
||||||
|
@ -1768,7 +1812,8 @@ proc write*(socket: UtpSocket, data: seq[byte]): Future[WriteResult] =
|
||||||
let retFuture = newFuture[WriteResult]("UtpSocket.write")
|
let retFuture = newFuture[WriteResult]("UtpSocket.write")
|
||||||
|
|
||||||
if (socket.state != Connected):
|
if (socket.state != Connected):
|
||||||
let res = WriteResult.err(WriteError(kind: SocketNotWriteable, currentState: socket.state))
|
let res = WriteResult.err(
|
||||||
|
WriteError(kind: SocketNotWriteable, currentState: socket.state))
|
||||||
retFuture.complete(res)
|
retFuture.complete(res)
|
||||||
return retFuture
|
return retFuture
|
||||||
|
|
||||||
|
@ -1845,8 +1890,8 @@ proc read*(socket: UtpSocket): Future[seq[byte]] =
|
||||||
|
|
||||||
return fut
|
return fut
|
||||||
|
|
||||||
# Check how many packets are still in the out going buffer, usefully for tests or
|
# Check how many packets are still in the out going buffer, usefully for tests
|
||||||
# debugging.
|
# or debugging.
|
||||||
proc numPacketsInOutGoingBuffer*(socket: UtpSocket): int =
|
proc numPacketsInOutGoingBuffer*(socket: UtpSocket): int =
|
||||||
var num = 0
|
var num = 0
|
||||||
for e in socket.outBuffer.items():
|
for e in socket.outBuffer.items():
|
||||||
|
@ -1858,11 +1903,12 @@ proc numPacketsInOutGoingBuffer*(socket: UtpSocket): int =
|
||||||
proc numOfBytesInFlight*(socket: UtpSocket): uint32 = socket.currentWindow
|
proc numOfBytesInFlight*(socket: UtpSocket): uint32 = socket.currentWindow
|
||||||
|
|
||||||
# Check how many bytes are in incoming buffer
|
# Check how many bytes are in incoming buffer
|
||||||
proc numOfBytesInIncomingBuffer*(socket: UtpSocket): uint32 = uint32(socket.offset)
|
proc numOfBytesInIncomingBuffer*(socket: UtpSocket): uint32 =
|
||||||
|
uint32(socket.offset)
|
||||||
|
|
||||||
# Check how many packets are still in the reorder buffer, useful for tests or
|
# Check how many packets are still in the reorder buffer, useful for tests or
|
||||||
# debugging.
|
# debugging. It throws assertion error when number of elements in buffer do not
|
||||||
# It throws assertion error when number of elements in buffer do not equal kept counter
|
# equal kept counter.
|
||||||
proc numPacketsInReorderedBuffer*(socket: UtpSocket): int =
|
proc numPacketsInReorderedBuffer*(socket: UtpSocket): int =
|
||||||
var num = 0
|
var num = 0
|
||||||
for e in socket.inBuffer.items():
|
for e in socket.inBuffer.items():
|
||||||
|
@ -1874,9 +1920,9 @@ proc numPacketsInReorderedBuffer*(socket: UtpSocket): int =
|
||||||
proc numOfEventsInEventQueue*(socket: UtpSocket): int = len(socket.eventQueue)
|
proc numOfEventsInEventQueue*(socket: UtpSocket): int = len(socket.eventQueue)
|
||||||
|
|
||||||
proc connectionId*[A](socket: UtpSocket[A]): uint16 =
|
proc connectionId*[A](socket: UtpSocket[A]): uint16 =
|
||||||
## Connection id is id which is used in first SYN packet which establishes the connection
|
## Connection id is the id which is used in first SYN packet which establishes
|
||||||
## so for Outgoing side it is actually its rcv_id, and for Incoming side it is
|
## the connection, so for `Outgoing` side it is actually its rcv_id, and for
|
||||||
## its snd_id
|
## `Incoming` side it is its snd_id.
|
||||||
case socket.direction
|
case socket.direction
|
||||||
of Incoming:
|
of Incoming:
|
||||||
socket.connectionIdSnd
|
socket.connectionIdSnd
|
||||||
|
@ -1902,11 +1948,11 @@ proc new[A](
|
||||||
): T =
|
): T =
|
||||||
let currentTime = getMonoTimestamp().moment
|
let currentTime = getMonoTimestamp().moment
|
||||||
|
|
||||||
# Initial max window size. Reference implementation uses value which enables one packet
|
# Initial max window size. Reference implementation uses value which allows
|
||||||
# to be transferred.
|
# one packet to be transferred.
|
||||||
# We use value two times higher as we do not yet have proper mtu estimation, and
|
# We use a value two times higher as we do not yet have proper mtu estimation,
|
||||||
# our impl should work over udp and discovery v5 (where proper estimation may be harder
|
# and our impl. should work over UDP and discovery v5 (where proper estimation
|
||||||
# as packets already have discoveryv5 envelope)
|
# may be harder as packets already have discovery v5 envelope).
|
||||||
let initMaxWindow = 2 * cfg.payloadSize
|
let initMaxWindow = 2 * cfg.payloadSize
|
||||||
T(
|
T(
|
||||||
remoteAddress: to,
|
remoteAddress: to,
|
||||||
|
@ -1987,10 +2033,10 @@ proc newIncomingSocket*[A](
|
||||||
let (initialState, initialTimeout) =
|
let (initialState, initialTimeout) =
|
||||||
if (cfg.incomingSocketReceiveTimeout.isNone()):
|
if (cfg.incomingSocketReceiveTimeout.isNone()):
|
||||||
# it does not matter what timeout value we put here, as socket will be in
|
# it does not matter what timeout value we put here, as socket will be in
|
||||||
# connected state without outgoing packets in buffer so any timeout hit will
|
# connected state without outgoing packets in buffer so any timeout hit
|
||||||
# just double rto without any penalties
|
# will just double rto without any penalties
|
||||||
# although we cannot use 0, as then timeout will be constantly re-set to 500ms
|
# although we cannot use 0, as then timeout will be constantly re-set to
|
||||||
# and there will be a lot of not useful work done
|
# 500ms and there will be a lot of not useful work done
|
||||||
(Connected, defaultInitialSynTimeout)
|
(Connected, defaultInitialSynTimeout)
|
||||||
else:
|
else:
|
||||||
let timeout = cfg.incomingSocketReceiveTimeout.unsafeGet()
|
let timeout = cfg.incomingSocketReceiveTimeout.unsafeGet()
|
||||||
|
@ -2020,7 +2066,8 @@ proc startIncomingSocket*(socket: UtpSocket) =
|
||||||
|
|
||||||
proc startOutgoingSocket*(socket: UtpSocket): Future[void] =
|
proc startOutgoingSocket*(socket: UtpSocket): Future[void] =
|
||||||
doAssert(socket.state == SynSent)
|
doAssert(socket.state == SynSent)
|
||||||
let packet = synPacket(socket.seqNr, socket.connectionIdRcv, socket.getRcvWindowSize())
|
let packet =
|
||||||
|
synPacket(socket.seqNr, socket.connectionIdRcv, socket.getRcvWindowSize())
|
||||||
debug "Sending SYN packet",
|
debug "Sending SYN packet",
|
||||||
seqNr = packet.header.seqNr,
|
seqNr = packet.header.seqNr,
|
||||||
connectionId = packet.header.connectionId
|
connectionId = packet.header.connectionId
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2020-2022 Status Research & Development GmbH
|
# Copyright (c) 2020-2023 Status Research & Development GmbH
|
||||||
# Licensed and distributed under either of
|
# Licensed and distributed under either of
|
||||||
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
@ -17,48 +17,49 @@ import
|
||||||
../../eth/keys,
|
../../eth/keys,
|
||||||
../stubloglevel
|
../stubloglevel
|
||||||
|
|
||||||
procSuite "Utp socket unit test":
|
procSuite "uTP socket tests":
|
||||||
let rng = newRng()
|
let
|
||||||
let testAddress = initTAddress("127.0.0.1", 9079)
|
rng = newRng()
|
||||||
let testBufferSize = 1024'u32
|
testAddress = initTAddress("127.0.0.1", 9079)
|
||||||
let defaultRcvOutgoingId = 314'u16
|
testBufferSize = 1024'u32
|
||||||
|
defaultRcvOutgoingId = 314'u16
|
||||||
|
|
||||||
proc packetsToBytes(packets: seq[Packet]): seq[byte] =
|
proc packetsToBytes(packets: seq[Packet]): seq[byte] =
|
||||||
var resultBytes = newSeq[byte]()
|
var bytes = newSeq[byte]()
|
||||||
for p in packets:
|
for p in packets:
|
||||||
resultBytes.add(p.payload)
|
bytes.add(p.payload)
|
||||||
return resultBytes
|
return bytes
|
||||||
|
|
||||||
asyncTest "Starting outgoing socket should send Syn packet":
|
asyncTest "Outgoing socket must send SYN packet":
|
||||||
let q = newAsyncQueue[Packet]()
|
let q = newAsyncQueue[Packet]()
|
||||||
let defaultConfig = SocketConfig.init()
|
let defaultConfig = SocketConfig.init()
|
||||||
let sock1 = newOutgoingSocket[TransportAddress](
|
let socket = newOutgoingSocket[TransportAddress](
|
||||||
testAddress,
|
testAddress,
|
||||||
initTestSnd(q),
|
initTestSnd(q),
|
||||||
defaultConfig,
|
defaultConfig,
|
||||||
defaultRcvOutgoingId,
|
defaultRcvOutgoingId,
|
||||||
rng[]
|
rng[]
|
||||||
)
|
)
|
||||||
let fut1 = sock1.startOutgoingSocket()
|
let fut = socket.startOutgoingSocket()
|
||||||
let initialPacket = await q.get()
|
let initialPacket = await q.get()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
initialPacket.header.pType == ST_SYN
|
initialPacket.header.pType == ST_SYN
|
||||||
initialPacket.header.wndSize == defaultConfig.optRcvBuffer
|
initialPacket.header.wndSize == defaultConfig.optRcvBuffer
|
||||||
|
|
||||||
await sock1.destroyWait()
|
await socket.destroyWait()
|
||||||
fut1.cancel()
|
fut.cancel()
|
||||||
|
|
||||||
asyncTest "Outgoing socket should re-send syn packet 2 times before declaring failure":
|
asyncTest "Outgoing socket should re-send SYN packet 2 times before declaring failure":
|
||||||
let q = newAsyncQueue[Packet]()
|
let q = newAsyncQueue[Packet]()
|
||||||
let sock1 = newOutgoingSocket[TransportAddress](
|
let socket = newOutgoingSocket[TransportAddress](
|
||||||
testAddress,
|
testAddress,
|
||||||
initTestSnd(q),
|
initTestSnd(q),
|
||||||
SocketConfig.init(milliseconds(100)),
|
SocketConfig.init(milliseconds(100)),
|
||||||
defaultRcvOutgoingId,
|
defaultRcvOutgoingId,
|
||||||
rng[]
|
rng[]
|
||||||
)
|
)
|
||||||
let fut1 = sock1.startOutgoingSocket()
|
let fut1 = socket.startOutgoingSocket()
|
||||||
let initialPacket = await q.get()
|
let initialPacket = await q.get()
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
@ -75,24 +76,24 @@ procSuite "Utp socket unit test":
|
||||||
resentSynPacket1.header.pType == ST_SYN
|
resentSynPacket1.header.pType == ST_SYN
|
||||||
|
|
||||||
# next timeout will should disconnect socket
|
# next timeout will should disconnect socket
|
||||||
await waitUntil(proc (): bool = sock1.isConnected() == false)
|
await waitUntil(proc (): bool = socket.isConnected() == false)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
not sock1.isConnected()
|
not socket.isConnected()
|
||||||
|
|
||||||
await sock1.destroyWait()
|
await socket.destroyWait()
|
||||||
fut1.cancel()
|
fut1.cancel()
|
||||||
|
|
||||||
asyncTest "Processing in order ack should make socket connected":
|
asyncTest "Processing in order ack should make socket connected":
|
||||||
let q = newAsyncQueue[Packet]()
|
let q = newAsyncQueue[Packet]()
|
||||||
let initialRemoteSeq = 10'u16
|
let initialRemoteSeq = 10'u16
|
||||||
|
|
||||||
let (sock1, packet) = connectOutGoingSocket(initialRemoteSeq, q)
|
let (socket, packet) = connectOutGoingSocket(initialRemoteSeq, q)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
sock1.isConnected()
|
socket.isConnected()
|
||||||
|
|
||||||
await sock1.destroyWait()
|
await socket.destroyWait()
|
||||||
|
|
||||||
asyncTest "Processing in order data packet should upload it to buffer and ack packet":
|
asyncTest "Processing in order data packet should upload it to buffer and ack packet":
|
||||||
let q = newAsyncQueue[Packet]()
|
let q = newAsyncQueue[Packet]()
|
||||||
|
@ -1418,9 +1419,9 @@ procSuite "Utp socket unit test":
|
||||||
let dataDropped = @[1'u8]
|
let dataDropped = @[1'u8]
|
||||||
let dataReceived = @[2'u8]
|
let dataReceived = @[2'u8]
|
||||||
|
|
||||||
let sock1 = newOutgoingSocket[TransportAddress](testAddress, initTestSnd(q), cfg, defaultRcvOutgoingId, rng[])
|
let socket = newOutgoingSocket[TransportAddress](testAddress, initTestSnd(q), cfg, defaultRcvOutgoingId, rng[])
|
||||||
|
|
||||||
asyncSpawn sock1.startOutgoingSocket()
|
asyncSpawn socket.startOutgoingSocket()
|
||||||
|
|
||||||
let initialPacket = await q.get()
|
let initialPacket = await q.get()
|
||||||
|
|
||||||
|
@ -1456,16 +1457,16 @@ procSuite "Utp socket unit test":
|
||||||
|
|
||||||
# even though @[1'u8] is received first, it should be dropped as socket is not
|
# even though @[1'u8] is received first, it should be dropped as socket is not
|
||||||
# yet in connected state
|
# yet in connected state
|
||||||
await sock1.processPacket(dpDropped)
|
await socket.processPacket(dpDropped)
|
||||||
await sock1.processPacket(responseAck)
|
await socket.processPacket(responseAck)
|
||||||
await sock1.processPacket(dpReceived)
|
await socket.processPacket(dpReceived)
|
||||||
|
|
||||||
let receivedData = await sock1.read(1)
|
let receivedData = await socket.read(1)
|
||||||
|
|
||||||
check:
|
check:
|
||||||
receivedData == dataReceived
|
receivedData == dataReceived
|
||||||
|
|
||||||
await sock1.destroyWait()
|
await socket.destroyWait()
|
||||||
|
|
||||||
asyncTest "Clean up all resources when closing due to timeout failure":
|
asyncTest "Clean up all resources when closing due to timeout failure":
|
||||||
let q = newAsyncQueue[Packet]()
|
let q = newAsyncQueue[Packet]()
|
||||||
|
|
Loading…
Reference in New Issue