Style fixes and comment improvements on uTP code (#623)

This commit is contained in:
Kim De Mey 2023-06-22 13:31:30 +02:00 committed by GitHub
parent 6dacb2ca5c
commit d74dc40bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 259 additions and 204 deletions

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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]()