nim-eth/tests/utp/test_utp_socket.nim
KonradStaniec 8ef6b13b1b
Add event loop to socket (#475)
- add eventLoop to control all incoming events
- change semantic of write to asynchronously block only when send buffer is full, and not when bytes do not fit into send window
- change handling of receive buffer, to start dropping packets if the reorder buffer and receive buffer are full. Old behaviour was to async block unless there is space which could lead to resource exhaustion attacks
2022-02-24 18:22:44 +01:00

1410 lines
39 KiB
Nim

# Copyright (c) 2020-2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * 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).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.used.}
import
std/[algorithm, random, sequtils, options],
chronos, bearssl, chronicles,
testutils/unittests,
./test_utils,
../../eth/utp/utp_router,
../../eth/utp/utp_socket,
../../eth/utp/packets,
../../eth/keys
procSuite "Utp socket unit test":
let rng = newRng()
let testAddress = initTAddress("127.0.0.1", 9079)
let testBufferSize = 1024'u32
let defaultRcvOutgoingId = 314'u16
proc packetsToBytes(packets: seq[Packet]): seq[byte] =
var resultBytes = newSeq[byte]()
for p in packets:
resultBytes.add(p.payload)
return resultBytes
asyncTest "Starting outgoing socket should send Syn packet":
let q = newAsyncQueue[Packet]()
let defaultConfig = SocketConfig.init()
let sock1 = newOutgoingSocket[TransportAddress](
testAddress,
initTestSnd(q),
defaultConfig,
defaultRcvOutgoingId,
rng[]
)
let fut1 = sock1.startOutgoingSocket()
let initialPacket = await q.get()
check:
initialPacket.header.pType == ST_SYN
initialPacket.header.wndSize == defaultConfig.optRcvBuffer
await sock1.destroyWait()
fut1.cancel()
asyncTest "Outgoing socket should re-send syn packet 2 times before declaring failure":
let q = newAsyncQueue[Packet]()
let sock1 = newOutgoingSocket[TransportAddress](
testAddress,
initTestSnd(q),
SocketConfig.init(milliseconds(100)),
defaultRcvOutgoingId,
rng[]
)
let fut1 = sock1.startOutgoingSocket()
let initialPacket = await q.get()
check:
initialPacket.header.pType == ST_SYN
let resentSynPacket = await q.get()
check:
resentSynPacket.header.pType == ST_SYN
let resentSynPacket1 = await q.get()
check:
resentSynPacket1.header.pType == ST_SYN
# next timeout will should disconnect socket
await waitUntil(proc (): bool = sock1.isConnected() == false)
check:
not sock1.isConnected()
await sock1.destroyWait()
fut1.cancel()
asyncTest "Processing in order ack should make socket connected":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (sock1, packet) = connectOutGoingSocket(initialRemoteSeq, q)
check:
sock1.isConnected()
await sock1.destroyWait()
asyncTest "Processing in order data packet should upload it to buffer and ack packet":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let data = @[1'u8, 2'u8, 3'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
let dataP1 =
dataPacket(
initalRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
await outgoingSocket.processPacket(dataP1)
let ack1 = await q.get()
check:
ack1.header.pType == ST_STATE
ack1.header.ackNr == initalRemoteSeqNr
let receivedBytes = await outgoingSocket.read(len(data))
check:
receivedBytes == data
await outgoingSocket.destroyWait()
asyncTest "Processing duplicated fresh data packet should ack it and stop processing":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let data = @[1'u8, 2'u8, 3'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
let dataP1 =
dataPacket(
initalRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
await outgoingSocket.processPacket(dataP1)
let ack1 = await q.get()
check:
ack1.header.pType == ST_STATE
ack1.header.ackNr == initalRemoteSeqNr
let receivedBytes = await outgoingSocket.read(len(data))
check:
receivedBytes == data
# remote re-send data packet, most probably due to lost ack
await outgoingSocket.processPacket(dataP1)
let ack2 = await q.get()
check:
ack2.header.pType == ST_STATE
ack2.header.ackNr == initalRemoteSeqNr
# we do not upload data one more time
outgoingSocket.numOfBytesInIncomingBuffer() == 0'u32
await outgoingSocket.destroyWait()
asyncTest "Processing out of order data packet should buffer it until receiving in order one":
# TODO test is valid until implementing selective acks
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let numOfPackets = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
var packets = generateDataPackets(numOfPackets, initalRemoteSeqNr, initialPacket.header.connectionId, initialPacket.header.seqNr, rng[])
let data = packetsToBytes(packets)
# start feeding packets from the last one
packets.reverse()
for p in packets:
await outgoingSocket.processPacket(p)
var sentAcks: seq[Packet] = @[]
for i in 0'u16..<numOfPackets:
let ack = await q.get()
sentAcks.add(ack)
# all packets except last one should be selective acks, without bumped ackNr
for i in 0'u16..<numOfPackets - 1:
check:
sentAcks[i].header.ackNr == initalRemoteSeqNr - 1
sentAcks[i].eack.isSome()
# last ack should be normal ack packet (not selective one), and it should ack
# all remaining packets
let lastAck = sentAcks[numOfPackets - 1]
check:
lastAck.header.pType == ST_STATE
# we are acking in one shot whole 10 packets
lastAck.header.ackNr == initalRemoteSeqNr + uint16(len(packets) - 1)
lastAck.eack.isNone()
let receivedData = await outgoingSocket.read(len(data))
check:
receivedData == data
await outgoingSocket.destroyWait()
asyncTest "Processing out of order data packet should ignore duplicated not ordered packets":
# TODO test is valid until implementing selective acks
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let numOfPackets = 3'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
var packets = generateDataPackets(numOfPackets, initalRemoteSeqNr, initialPacket.header.connectionId, initialPacket.header.seqNr, rng[])
let data = packetsToBytes(packets)
# start feeding packets from the last one
packets.reverse()
# Process last packet additional two times, it should be ignored by processing logic
await outgoingSocket.processPacket(packets[0])
await outgoingSocket.processPacket(packets[0])
for p in packets:
await outgoingSocket.processPacket(p)
var sentAcks: seq[Packet] = @[]
for i in 0'u16..<numOfPackets:
let ack = await q.get()
sentAcks.add(ack)
# all packets except last one should be selective acks, without bumped ackNr
for i in 0'u16..<numOfPackets - 1:
check:
sentAcks[i].header.ackNr == initalRemoteSeqNr - 1
sentAcks[i].eack.isSome()
# last ack should be normal ack packet (not selective one), and it should ack
# all remaining packets
let lastAck = sentAcks[numOfPackets - 1]
check:
lastAck.header.pType == ST_STATE
# we are acking in one shot whole 10 packets
lastAck.header.ackNr == initalRemoteSeqNr + uint16(len(packets) - 1)
lastAck.eack.isNone()
let receivedData = await outgoingSocket.read(len(data))
check:
receivedData == data
await outgoingSocket.destroyWait()
asyncTest "Processing packets in random order":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
var packets = generateDataPackets(30, initalRemoteSeqNr, initialPacket.header.connectionId, initialPacket.header.seqNr, rng[])
let data = packetsToBytes(packets)
# start feeding packets from the last one
randomize()
packets.shuffle()
for p in packets:
await outgoingSocket.processPacket(p)
let receivedData = await outgoingSocket.read(len(data))
check:
# with packets totally out of order we cannont assert on acks
# as they can be fired at any point. What matters is that data is passed
# in same order as received.
receivedData == data
await outgoingSocket.destroyWait()
asyncTest "Ignoring totally out of order packet":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
var packets = generateDataPackets(1025, initalRemoteSeqNr, initialPacket.header.connectionId, initialPacket.header.seqNr, rng[])
await outgoingSocket.processPacket(packets[1024])
await outgoingSocket.processPacket(packets[1023])
# give some time to process those packets
await sleepAsync(milliseconds(500))
check:
outgoingSocket.numPacketsInReordedBuffer() == 1
await outgoingSocket.destroyWait()
asyncTest "Writing small enough data should produce 1 data packet":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = @[1'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let bytesWritten = await outgoingSocket.write(dataToWrite)
check:
bytesWritten.get() == len(dataToWrite)
let sentPacket = await q.get()
check:
outgoingSocket.numPacketsInOutGoingBuffer() == 1
sentPacket.header.pType == ST_DATA
sentPacket.header.seqNr == initialPacket.header.seqNr + 1
sentPacket.payload == dataToWrite
# ackNr in state packet, is set to sentPacket.header.seqNr which means remote
# side processed out packet
let responseAck =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
sentPacket.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(responseAck)
await waitUntil(proc (): bool = outgoingSocket.numPacketsInOutGoingBuffer() == 0)
check:
outgoingSocket.numPacketsInOutGoingBuffer() == 0
await outgoingSocket.destroyWait()
proc ackAllPacket(
socket: UtpSocket[TransportAddress],
queue: AsyncQueue[Packet],
initialRemoteSeq: uint16): Future[void] {.async.} =
try:
while true:
let sentPacket = await queue.get()
let ack = ackPacket(
initialRemoteSeq,
sentPacket.header.connectionId,
sentPacket.header.seqNr,
1024 * 1024,
1000'u32
)
await socket.processPacket(ack)
except CancelledError:
discard
asyncTest "Hitting RTO timeout with packets in flight should not decay window":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
# lot of data which will generate at least 5 packets
let bigDataTowWrite = generateByteArray(rng[], 10000)
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let acker = outgoingSocket.ackAllPacket(q, initialRemoteSeq)
let bytesWritten = await outgoingSocket.write(bigDataTowWrite)
check:
bytesWritten.get() == len(bigDataTowWrite)
await waitUntil(proc (): bool = outgoingSocket.numPacketsInOutGoingBuffer() == 0)
let maxWindowAfterSuccesfulSends = outgoingSocket.currentMaxWindowSize()
check:
# after processing a lot of data, our window size should be a lot bigger than our packet size
maxWindowAfterSuccesfulSends > uint32(outgoingSocket.getPacketSize())
# cancel acking process, next writes will for sure timeout
await acker.cancelAndWait()
# data which fits one packet and will timeout
let smallerData = generateByteArray(rng[], 100)
let bytesWritten1 = await outgoingSocket.write(smallerData)
# ignore standard sent packet
discard await q.get()
check:
bytesWritten1.get() == len(smallerData)
# ignore also first re-send
discard await q.get()
let maxWindowAfterTimeout = outgoingSocket.currentMaxWindowSize()
check:
# After standard timeout window should not decay and must be bigger than packet size
maxWindowAfterTimeout > uint32(outgoingSocket.getPacketSize())
maxWindowAfterTimeout == maxWindowAfterSuccesfulSends
await outgoingSocket.destroyWait()
asyncTest "Blocked writing futures should be properly finished when socket is closed":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite1 = @[0'u8]
let dataToWrite2 = @[1'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q, cfg = SocketConfig.init(optSndBuffer = 0))
let writeFut1 = outgoingSocket.write(dataToWrite1)
let writeFut2 = outgoingSocket.write(dataToWrite2)
# wait a little to show that futures are not progressing
await sleepAsync(seconds(1))
check:
not writeFut1.finished()
not writeFut2.finished()
outgoingSocket.destroy()
yield writeFut1
yield writeFut2
check:
writeFut1.completed()
writeFut2.completed()
writeFut1.read().isErr()
writeFut2.read().isErr()
await outgoingSocket.destroyWait()
asyncTest "Cancelled write futures should not be processed if cancelled before processing":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite1 = @[0'u8]
let dataToWrite2 = @[1'u8]
let dataToWrite3 = @[2'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q, 0)
# only writeFut1 will progress as to processing stage, writeFut2 and writeFut3
# will be blocked in queue
let writeFut1 = outgoingSocket.write(dataToWrite1)
let writeFut2 = outgoingSocket.write(dataToWrite2)
let writeFut3 = outgoingSocket.write(dataToWrite3)
# user decided to cancel second write
await writeFut2.cancelAndWait()
# remote increased wnd size enough for all writes
let someAckFromRemote =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
10,
0
)
await outgoingSocket.processPacket(someAckFromRemote)
yield writeFut1
yield writeFut2
yield writeFut3
check:
writeFut1.completed()
writeFut2.cancelled()
writeFut3.completed()
let p1 = await q.get()
let p2 = await q.get
check:
# we produce only two data packets as write with dataToWrite2 was cancelled
p1.payload == dataToWrite1
p2.payload == dataToWrite3
await outgoingSocket.destroyWait()
asyncTest "Socket should re-send data packet configurable number of times before declaring failure":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let outgoingSocket = newOutgoingSocket[TransportAddress](
testAddress,
initTestSnd(q),
SocketConfig.init(milliseconds(3000), 2),
defaultRcvOutgoingId,
rng[]
)
let fut1 = outgoingSocket.startOutgoingSocket()
let initialPacket = await q.get()
check:
initialPacket.header.pType == ST_SYN
let responseAck =
ackPacket(
initalRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(responseAck)
await waitUntil(proc (): bool = outgoingSocket.isConnected())
check:
outgoingSocket.isConnected()
let dataToWrite = @[1'u8]
let bytesWritten = await outgoingSocket.write(dataToWrite)
check:
bytesWritten.get() == len(dataToWrite)
let sentPacket = await q.get()
check:
outgoingSocket.numPacketsInOutGoingBuffer() == 1
sentPacket.header.pType == ST_DATA
sentPacket.header.seqNr == initialPacket.header.seqNr + 1
sentPacket.payload == dataToWrite
let reSend1 = await q.get()
check:
outgoingSocket.numPacketsInOutGoingBuffer() == 1
reSend1.header.pType == ST_DATA
reSend1.header.seqNr == initialPacket.header.seqNr + 1
reSend1.payload == dataToWrite
let reSend2 = await q.get()
check:
outgoingSocket.numPacketsInOutGoingBuffer() == 1
reSend2.header.pType == ST_DATA
reSend2.header.seqNr == initialPacket.header.seqNr + 1
reSend2.payload == dataToWrite
# next timeout will should disconnect socket
await waitUntil(proc (): bool = outgoingSocket.isConnected() == false)
check:
not outgoingSocket.isConnected()
len(q) == 0
await outgoingSocket.destroyWait()
asyncTest "Processing in order fin should make socket reach eof and ack this packet":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let finP =
finPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(finP)
let ack1 = await q.get()
check:
ack1.header.pType == ST_STATE
outgoingSocket.atEof()
await outgoingSocket.destroyWait()
asyncTest "Processing out of order fin should buffer it until receiving all remaining packets":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let data = @[1'u8, 2'u8, 3'u8]
let data1 = @[4'u8, 5'u8, 6'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let readF = outgoingSocket.read()
let dataP =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
let dataP1 =
dataPacket(
initialRemoteSeq + 1,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data1,
0
)
let finP =
finPacket(
initialRemoteSeq + 2,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(finP)
check:
not readF.finished()
not outgoingSocket.atEof()
await outgoingSocket.processPacket(dataP1)
check:
not readF.finished()
not outgoingSocket.atEof()
await outgoingSocket.processPacket(dataP)
let bytes = await readF
check:
readF.finished()
outgoingSocket.atEof()
bytes == concat(data, data1)
await outgoingSocket.destroyWait()
asyncTest "Socket should ignore data past eof packet":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let data = @[1'u8, 2'u8, 3'u8]
let data1 = @[4'u8, 5'u8, 6'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let readF = outgoingSocket.read()
let dataP =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
let finP =
finPacket(
initialRemoteSeq + 1,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
0
)
# dataP1 has seqNr larger than fin, there fore it should be considered past eof and never passed
# to user of library
let dataP1 =
dataPacket(
initialRemoteSeq + 2,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data1,
0
)
await outgoingSocket.processPacket(finP)
check:
not readF.finished()
not outgoingSocket.atEof()
# it is out of order dataP1 (as we still not processed dataP packet)
await outgoingSocket.processPacket(dataP1)
check:
not readF.finished()
not outgoingSocket.atEof()
await outgoingSocket.processPacket(dataP)
# it is in order dataP1, as we have now processed dataP + fin which came before
# but it is past eof so it should be ignored
await outgoingSocket.processPacket(dataP1)
let bytes = await readF
check:
readF.finished()
outgoingSocket.atEof()
bytes == concat(data)
await outgoingSocket.destroyWait()
asyncTest "Calling close should send fin packet":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
outgoingSocket.close()
let sendFin = await q.get()
check:
sendFin.header.pType == ST_FIN
await outgoingSocket.destroyWait()
asyncTest "Receiving ack for fin packet should destroy socket":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
outgoingSocket.close()
let sendFin = await q.get()
check:
sendFin.header.pType == ST_FIN
let responseAck =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
sendFin.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(responseAck)
await waitUntil(proc (): bool = not outgoingSocket.isConnected())
check:
not outgoingSocket.isConnected()
await outgoingSocket.destroyWait()
asyncTest "Trying to write data onto closed socket should return error":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
await outgoingSocket.destroyWait()
let writeResult = await outgoingSocket.write(@[1'u8])
check:
writeResult.isErr()
let error = writeResult.error()
check:
error.kind == SocketNotWriteable
error.currentState == Destroy
asyncTest "Trying to write data onto closed socket which sent fin":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
outgoingSocket.close()
let writeResult = await outgoingSocket.write(@[1'u8])
check:
writeResult.isErr()
let error = writeResult.error()
check:
error.kind == FinSent
await outgoingSocket.destroyWait()
asyncTest "Processing data packet should update window size accordingly and use it in all send packets":
let q = newAsyncQueue[Packet]()
let initialRemoteSeqNr = 10'u16
let initialRcvBufferSize = 10'u32
let data = @[1'u8, 2'u8, 3'u8]
let sCfg = SocketConfig.init(optRcvBuffer = initialRcvBufferSize)
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeqNr, q, testBufferSize, sCfg)
let dataP1 =
dataPacket(
initialRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
await outgoingSocket.processPacket(dataP1)
let ack1 = await q.get()
check:
ack1.header.pType == ST_STATE
ack1.header.ackNr == initialRemoteSeqNr
ack1.header.wndSize == initialRcvBufferSize - uint32(len(data))
let written = await outgoingSocket.write(data)
let sentData = await q.get()
check:
sentData.header.pType == ST_DATA
sentData.header.wndSize == initialRcvBufferSize - uint32(len(data))
outgoingSocket.close()
let sentFin = await q.get()
check:
sentFin.header.pType == ST_FIN
sentFin.header.wndSize == initialRcvBufferSize - uint32(len(data))
await outgoingSocket.destroyWait()
asyncTest "Reading data from the buffer shoud increase receive window":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let initialRcvBufferSize = 10'u32
let data = @[1'u8, 2'u8, 3'u8]
let sCfg = SocketConfig.init(optRcvBuffer = initialRcvBufferSize)
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q, testBufferSize, sCfg)
let dataP1 =
dataPacket(
initalRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data,
0
)
await outgoingSocket.processPacket(dataP1)
let ack1 = await q.get()
check:
ack1.header.pType == ST_STATE
ack1.header.ackNr == initalRemoteSeqNr
ack1.header.wndSize == initialRcvBufferSize - uint32(len(data))
let readData = await outgoingSocket.read(data.len())
check:
readData == data
discard await outgoingSocket.write(data)
let sentData = await q.get()
check:
sentData.header.pType == ST_DATA
# we have read all data from rcv buffer, advertised window should go back to
# initial size
sentData.header.wndSize == initialRcvBufferSize
await outgoingSocket.destroyWait()
asyncTest "Socket should ignore packets with bad ack number":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let data1 = @[1'u8, 2'u8, 3'u8]
let data2 = @[4'u8, 5'u8, 6'u8]
let data3 = @[7'u8, 7'u8, 9'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
# data packet with ack nr set above our seq nr i.e packet from the future
let dataFuture =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr + 1,
testBufferSize,
data1,
0
)
# data packet wth ack number set below out ack window i.e packet too old
let dataTooOld =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr - allowedAckWindow - 1,
testBufferSize,
data2,
0
)
let dataOk =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
data3,
0
)
await outgoingSocket.processPacket(dataFuture)
await outgoingSocket.processPacket(dataTooOld)
await outgoingSocket.processPacket(dataOk)
let receivedBytes = await outgoingSocket.read(data3.len)
check:
# data1 and data2 were sent in bad packets we should only receive data3
receivedBytes == data3
await outgoingSocket.destroyWait()
asyncTest "Writing data should increase current bytes window":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = @[1'u8, 2, 3, 4, 5]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
discard await outgoingSocket.write(dataToWrite)
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite)
discard await outgoingSocket.write(dataToWrite)
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite) + len(dataToWrite)
await outgoingSocket.destroyWait()
asyncTest "Acking data packet should decrease current bytes window":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = @[1'u8, 2, 3, 4, 5]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
discard await outgoingSocket.write(dataToWrite)
let sentPacket = await q.get()
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite)
discard await outgoingSocket.write(dataToWrite)
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite) + len(dataToWrite)
let responseAck =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
sentPacket.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(responseAck)
await waitUntil(proc (): bool = int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite))
check:
# only first packet has been acked so there should still by 5 bytes left
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite)
await outgoingSocket.destroyWait()
asyncTest "Timeout packets should decrease bytes window":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = @[1'u8, 2, 3]
let dataToWrite1 = @[6'u8, 7, 8, 9, 10]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
discard await outgoingSocket.write(dataToWrite)
let sentPacket = await q.get()
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite)
discard await outgoingSocket.write(dataToWrite1)
let sentPacket1 = await q.get()
check:
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite) + len(dataToWrite1)
# after timeout oldest packet will be immediatly re-sent
let reSentFirstPacket = await q.get()
check:
reSentFirstPacket.payload == sentPacket.payload
# first packet has been re-sent so its payload still counts to bytes in flight
# second packet has been marked as missing, therefore its bytes are not counting
# to bytes in flight
int(outgoingSocket.numOfBytesInFlight) == len(dataToWrite)
await outgoingSocket.destroyWait()
asyncTest "Writing data should asynchronously block until there is enough space in snd buffer":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = generateByteArray(rng[], 1001)
# remote is initialized with buffer to small to handle whole payload
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q, cfg = SocketConfig.init(optSndBuffer = 1000))
let writeFut = outgoingSocket.write(dataToWrite)
# wait some time to check future is not finished
await sleepAsync(seconds(2))
# write is not finished as future is blocked from progressing due to to full
# send buffer
check:
not writeFut.finished()
let someAckFromRemote =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr + 1,
testBufferSize,
0
)
await outgoingSocket.processPacket(someAckFromRemote)
# only after processing ack write will progress
let writeResult = await writeFut
check:
writeResult.isOK()
await outgoingSocket.destroyWait()
asyncTest "Writing data should not progress in case of timeouting packets and small snd window":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWirte = 1160
# remote is initialized with buffer to small to handle whole payload
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q, cfg = SocketConfig.init(optSndBuffer = 1160))
let twoPacketData = generateByteArray(rng[], int(dataToWirte))
let writeResult = await outgoingSocket.write(twoPacketData)
check:
writeResult.isOk()
# this write will not progress as snd buffer is full
let writeFut = outgoingSocket.write(twoPacketData)
# we wait for packets to timeout
await sleepAsync(seconds(2))
check:
not writeFut.finished()
await outgoingSocket.destroyWait()
asyncTest "Writing data should respect remote rcv window size":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let dataToWrite = @[1'u8, 2, 3, 4, 5]
# remote is initialized with buffer to small to handle whole payload
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let remoteRcvWindowSize = uint32(outgoingSocket.getPacketSize())
let someAckFromRemote =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
remoteRcvWindowSize,
0
)
# we are using ack from remote to setup our snd window size to one packet size on one packet
await outgoingSocket.processPacket(someAckFromRemote)
let twoPacketData = generateByteArray(rng[], int(2 * remoteRcvWindowSize))
let writeFut = outgoingSocket.write(twoPacketData)
let firstAckFromRemote =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr + 1,
remoteRcvWindowSize,
0
)
let packet = await q.get()
check:
packet.header.pType == ST_DATA
uint32(len(packet.payload)) == remoteRcvWindowSize
let packet1Fut = q.get()
await sleepAsync(milliseconds(500))
check:
not packet1Fut.finished()
await outgoingSocket.processPacket(firstAckFromRemote)
# packet is sent only after first packet is acked
let packet1 = await packet1Fut
check:
packet1.header.pType == ST_DATA
packet1.header.seqNr == packet.header.seqNr + 1
writeFut.finished
await outgoingSocket.destroyWait()
asyncTest "Remote window should be reseted to minimal value after configured amount of time":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let someData = @[1'u8]
let (outgoingSocket, packet) =
connectOutGoingSocket(
initialRemoteSeq,
q,
remoteReceiveBuffer = 0,
cfg = SocketConfig.init(
remoteWindowResetTimeout = seconds(3)
)
)
check:
outgoingSocket.isConnected()
# write result will be successfull as send buffer has space
let writeResult = await outgoingSocket.write(someData)
# this will finish in seconds(3) as only after this time window will be set to min value
let p = await q.get()
check:
writeResult.isOk()
p.payload == someData
await outgoingSocket.destroyWait()
asyncTest "Writing data should respect max snd buffer option":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let someData1 = @[1'u8]
let someData2 = @[2'u8]
let (outgoingSocket, initialPacket) =
connectOutGoingSocket(
initialRemoteSeq,
q,
cfg = SocketConfig.init(
optSndBuffer = 1
)
)
check:
outgoingSocket.isConnected()
# snd buffer got 1 byte of space so this future shold finish
let write1 = await outgoingSocket.write(someData1)
let writeFut2 = outgoingSocket.write(someData2)
# wait until 2 re-sends to check we do not accidently free buffer during re-sends
discard await q.get()
discard await q.get()
let firstPacket = await q.get()
check:
# this write still cannot progress as 1st write is not acked
not writeFut2.finished()
let someAckFromRemote =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr + 1,
10,
0
)
# acks first write, so there is space in buffer for new data and second
# write should progress
await outgoingSocket.processPacket(someAckFromRemote)
yield writeFut2
let secondPacket = await q.get()
check:
writeFut2.finished()
firstPacket.payload == someData1
secondPacket.payload == someData2
await outgoingSocket.destroyWait()
asyncTest "Socket should inform remote about its delay":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let dataP1 =
dataPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
@[1'u8],
0
)
check:
outgoingSocket.isConnected()
# necessary to avoid timestampDiff near 0 and flaky tests
await sleepAsync(milliseconds(50))
await outgoingSocket.processPacket(dataP1)
let socketAck = await q.get()
check:
socketAck.header.timestampDiff > 0
await outgoingSocket.destroyWait()
asyncTest "Re-sent packet should have updated timestamps and ack numbers":
let q = newAsyncQueue[Packet]()
let initalRemoteSeqNr = 10'u16
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initalRemoteSeqNr, q)
let writeResult = await outgoingSocket.write(@[1'u8])
check:
writeResult.isOk()
let firstSend = await q.get()
let secondSend = await q.get()
check:
# there was sometime between resend but no packet from remote
# so timestamp should be updated but not ackNr
secondSend.header.timestamp > firstSend.header.timestamp
firstSend.header.ackNr == secondSend.header.ackNr
let dataP1 =
dataPacket(
initalRemoteSeqNr,
initialPacket.header.connectionId,
initialPacket.header.seqNr,
testBufferSize,
@[1'u8],
0
)
await outgoingSocket.processPacket(dataP1)
let fastResend = await q.get()
let ack = await q.get()
check:
ack.header.pType == ST_STATE
let thirdSend = await q.get()
check:
# as there was some incoming data between resend, both timestamp and ackNr
# should be updated
thirdSend.header.timestamp > secondSend.header.timestamp
thirdSend.header.ackNr > secondSend.header.ackNr
await outgoingSocket.destroyWait()
asyncTest "Should support fast timeout ":
let q = newAsyncQueue[Packet]()
let initialRemoteSeq = 10'u16
# small writes to make sure it will be 3 different packets
let dataToWrite1 = @[1'u8]
let dataToWrite2 = @[1'u8]
let dataToWrite3 = @[1'u8]
let (outgoingSocket, initialPacket) = connectOutGoingSocket(initialRemoteSeq, q)
let writeRes1 = await outgoingSocket.write(dataToWrite1)
let writeRes2 = await outgoingSocket.write(dataToWrite2)
let writeRes3 = await outgoingSocket.write(dataToWrite3)
check:
writeRes1.isOk()
writeRes2.isOk()
writeRes3.isOk()
# drain queue of all sent packets
let sent1 = await q.get()
let sent2 = await q.get()
let sent3 = await q.get()
# wait for first timeout. Socket will enter fast timeout mode
let reSent1 = await q.get()
check:
# check that re-sent packet is the oldest one
reSent1.payload == sent1.payload
reSent1.header.seqNr == sent1.header.seqNr
# ack which will ack our re-sent packet
let responseAck =
ackPacket(
initialRemoteSeq,
initialPacket.header.connectionId,
reSent1.header.seqNr,
testBufferSize,
0
)
await outgoingSocket.processPacket(responseAck)
let fastResentPacket = await q.get()
check:
# second packet is now oldest unacked packet so it should be the one which
# is send during fast resend
fastResentPacket.payload == sent2.payload
fastResentPacket.header.seqNr == sent2.header.seqNr
# duplicate ack, processing it should not fast-resend any packet
await outgoingSocket.processPacket(responseAck)
let resent3 = await q.get()
check:
# in next timeout cycle packet nr3 is the only one waiting for re-send
resent3.payload == sent3.payload
resent3.header.seqNr == sent3.header.seqNr
await outgoingSocket.destroyWait()