Improve uTP decoded packet logs + style and comment clean-up (#593)

* Improve uTP decoded packet logs + style and comment clean-up

* Don't test for the exact error strings in uTP decode + clean-up
This commit is contained in:
Kim De Mey 2023-03-14 17:17:39 +01:00 committed by GitHub
parent 72c9858927
commit 29b14749fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 113 deletions

View File

@ -1,9 +1,12 @@
# Copyright (c) 2020-2021 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).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
# uTP packet format as specified in:
# https://www.bittorrent.org/beps/bep_0029.html
{.push raises: [Defect].} {.push raises: [Defect].}
import import
@ -37,14 +40,9 @@ type
extension*: uint8 extension*: uint8
connectionId*: uint16 connectionId*: uint16
timestamp*: MicroSeconds timestamp*: MicroSeconds
# This is the difference between the local time, at the time the last packet
# was received, and the timestamp in this last received packet
timestampDiff*: MicroSeconds timestampDiff*: MicroSeconds
# The window size is the number of bytes currently in-flight, i.e. sent but not acked
# When sending packets, this should be set to the number of bytes left in the socket's receive buffer.
wndSize*: uint32 wndSize*: uint32
seqNr*: uint16 seqNr*: uint16
# sequence number the sender of the packet last received in the other direction
ackNr*: uint16 ackNr*: uint16
SelectiveAckExtension* = object SelectiveAckExtension* = object
@ -59,22 +57,21 @@ type
moment*: Moment moment*: Moment
timestamp*: uint32 timestamp*: uint32
# Important timing assumptions for utp protocol here: # Important timing assumptions for uTP protocol here:
# 1. Microsecond precisions # 1. Microsecond precisions
# 2. Monotonicity # 2. Monotonicity
# Reference lib have a lot of checks to assume that this is monotonic on # Reference lib has a lot of checks to assume that this is monotonic on
# every system, and warnings when monotonic clock is not available. # every system, and warnings when monotonic clock is not available.
proc getMonoTimestamp*(): TimeStampInfo = proc getMonoTimestamp*(): TimeStampInfo =
let currentMoment = Moment.now() let currentMoment = Moment.now()
# Casting this value from int64 to uin32, my lead to some sudden spikes in # Casting this value from int64 to uint32, may lead to some sudden spikes in
# timestamp numeric values i.e it is possible that timestamp can suddenly change # timestamp numeric values, i.e it is possible that the timestamp will
# from 4294967296 to for example 10, this may lead to sudden spikes in # suddenly change from 4294967296 to for example 10. This may lead to sudden
# calculated delays # spikes in calculated delays.
# uTP implementation is resistant to those spikes are as it keeps history of # The uTP implementation is resistant to those spikes are as it keeps a
# few last delays on uses smallest one for calculating ledbat window. # history of several last delays and uses the smallest one for calculating
# so any outlier huge value will be ignored # the ledbat window, thus any outlier value will be ignored.
#
let timestamp = uint32((currentMoment - zeroMoment).microseconds()) let timestamp = uint32((currentMoment - zeroMoment).microseconds())
TimeStampInfo(moment: currentMoment, timestamp: timestamp) TimeStampInfo(moment: currentMoment, timestamp: timestamp)
@ -86,7 +83,7 @@ proc randUint16*(rng: var HmacDrbgContext): uint16 =
proc randUint32*(rng: var HmacDrbgContext): uint32 = proc randUint32*(rng: var HmacDrbgContext): uint32 =
uint32(rand(rng, int(high(uint32)))) uint32(rand(rng, int(high(uint32))))
proc encodeTypeVer(h: PacketHeaderV1): uint8 = func encodeTypeVer(h: PacketHeaderV1): uint8 =
var typeVer = 0'u8 var typeVer = 0'u8
let typeOrd = uint8(ord(h.pType)) let typeOrd = uint8(ord(h.pType))
typeVer = (typeVer and 0xf0) or (h.version and 0xf) typeVer = (typeVer and 0xf0) or (h.version and 0xf)
@ -109,7 +106,7 @@ proc encodeHeaderStream(s: var OutputStream, h: PacketHeaderV1) =
proc encodeExtensionStream(s: var OutputStream, e: SelectiveAckExtension) = proc encodeExtensionStream(s: var OutputStream, e: SelectiveAckExtension) =
try: try:
# writing 0 as there is not further extensions after selective ack # writing always 0 as there are no other extensions (only selective ack)
s.write(0'u8) s.write(0'u8)
s.write(acksArrayLength) s.write(acksArrayLength)
s.write(e.acks) s.write(e.acks)
@ -130,14 +127,14 @@ proc encodePacket*(p: Packet): seq[byte] =
# This should not happen in case of in-memory streams # This should not happen in case of in-memory streams
raiseAssert e.msg raiseAssert e.msg
proc decodePacket*(bytes: openArray[byte]): Result[Packet, string] = func decodePacket*(bytes: openArray[byte]): Result[Packet, string] =
let receivedBytesLength = len(bytes) let receivedBytesLength = len(bytes)
if receivedBytesLength < minimalHeaderSize: if receivedBytesLength < minimalHeaderSize:
return err("invalid header size") return err("Invalid header size")
let version = bytes[0] and 0xf let version = bytes[0] and 0xf
if version != protocolVersion: if version != protocolVersion:
return err("invalid packet version") return err("Invalid packet version")
var kind: PacketType var kind: PacketType
if not checkedEnumAssign(kind, (bytes[0] shr 4)): if not checkedEnumAssign(kind, (bytes[0] shr 4)):
@ -162,30 +159,35 @@ proc decodePacket*(bytes: openArray[byte]): Result[Packet, string] =
) )
if extensionByte == 0: if extensionByte == 0:
# packet without any extensions # packet without extensions
let payload = let payload =
if (receivedBytesLength == minimalHeaderSize): if (receivedBytesLength == minimalHeaderSize):
@[] @[]
else: else:
bytes[minimalHeaderSize..^1] bytes[minimalHeaderSize..^1]
return ok(Packet(header: header, eack: none[SelectiveAckExtension](), payload: payload)) return ok(Packet(
header: header,
eack: none[SelectiveAckExtension](),
payload: payload))
else: else:
# packet with selective ack extension # packet with the selective ack extension
if (receivedBytesLength < minimalHeaderSizeWithSelectiveAck): if (receivedBytesLength < minimalHeaderSizeWithSelectiveAck):
return err("Packet too short for selective ack extension") return err("Packet too short for selective ack extension: " &
"len = " & $receivedBytesLength)
let nextExtension = bytes[20] let nextExtension = bytes[20]
let extLength = bytes[21] let extLength = bytes[21]
# As selective ack is only supported extension the byte for nextExtension # The byte for nextExtension must be 0 as selective ack is currently the
# must be equal to 0. # only supported extension.
# As for extLength, specification says that it must be at least 4, and in multiples of 4 # For extLength, the specification states that it must be at least 4, and
# but reference implementation always uses 4 bytes bit mask which makes sense # in multiples of 4. However, the reference implementation always uses a 4
# as 4byte bit mask is able to ack 32 packets in the future which is more than enough # bytes bit mask, which makes sense as a 4 byte bit mask is able to ack
# 32 packets in the future, which is sounds more than enough.
if (nextExtension != 0 or extLength != 4): if (nextExtension != 0 or extLength != 4):
return err("Bad format of selective ack extension") return err("Bad format of selective ack extension: " &
"extension = " & $nextExtension & " len = " & $extLength)
let extension = SelectiveAckExtension( let extension = SelectiveAckExtension(
acks: toArray(4, bytes.toOpenArray(22, 25)) acks: toArray(4, bytes.toOpenArray(22, 25))
@ -199,19 +201,21 @@ proc decodePacket*(bytes: openArray[byte]): Result[Packet, string] =
return ok(Packet(header: header, eack: some(extension), payload: payload)) return ok(Packet(header: header, eack: some(extension), payload: payload))
proc modifyTimeStampAndAckNr*(packetBytes: var seq[byte], newTimestamp: uint32, newAckNr: uint16) = proc modifyTimeStampAndAckNr*(
## Modifies timestamp and ack nr of already encoded packets. Those fields should be packetBytes: var seq[byte], newTimestamp: uint32, newAckNr: uint16) =
## filled right before sending, so when re-sending the packet we would like to update ## Modifies timestamp and ack nr of already encoded packets. These fields
## it without decoding and re-encoding the packet once again ## must be filled right before sending, so when re-sending the packet they
## can be updated without decoding and re-encoding the packet.
doAssert(len(packetBytes) >= minimalHeaderSize) doAssert(len(packetBytes) >= minimalHeaderSize)
packetBytes[4..7] = toBytesBE(newTimestamp) packetBytes[4..7] = toBytesBE(newTimestamp)
packetBytes[18..19] = toBytesBE(newAckNr) packetBytes[18..19] = toBytesBE(newAckNr)
# connectionId - should be random not already used number proc synPacket*(
# bufferSize - should be pre configured initial buffer size for socket seqNr: uint16, rcvConnectionId: uint16, bufferSize: uint32): Packet =
# SYN packets are special, and should have the receive ID in the connid field, # rcvConnectionId - should be a random, not already used, number
# instead of conn_id_send. # bufferSize - should be a pre-configured initial buffer size for the socket
proc synPacket*(seqNr: uint16, rcvConnectionId: uint16, bufferSize: uint32): Packet = # SYN packets are special and have the conn_id_recv in the conn_id field,
# instead of conn_id_send.
let h = PacketHeaderV1( let h = PacketHeaderV1(
pType: ST_SYN, pType: ST_SYN,
version: protocolVersion, version: protocolVersion,
@ -221,8 +225,7 @@ proc synPacket*(seqNr: uint16, rcvConnectionId: uint16, bufferSize: uint32): Pac
timestampDiff: 0'u32, timestampDiff: 0'u32,
wndSize: bufferSize, wndSize: bufferSize,
seqNr: seqNr, seqNr: seqNr,
# Initially we did not receive any acks ackNr: 0'u16 # At start, no acks have been received
ackNr: 0'u16
) )
Packet(header: h, eack: none[SelectiveAckExtension](), payload: @[]) Packet(header: h, eack: none[SelectiveAckExtension](), payload: @[])
@ -267,8 +270,7 @@ proc dataPacket*(
let h = PacketHeaderV1( let h = PacketHeaderV1(
pType: ST_DATA, pType: ST_DATA,
version: protocolVersion, version: protocolVersion,
# data packets always have extension field set to 0 extension: 0'u8, # data packets always have extension field set to 0
extension: 0'u8,
connectionId: sndConnectionId, connectionId: sndConnectionId,
timestamp: getMonoTimestamp().timestamp, timestamp: getMonoTimestamp().timestamp,
timestampDiff: timestampDiff, timestampDiff: timestampDiff,
@ -279,16 +281,16 @@ proc dataPacket*(
Packet(header: h, eack: none[SelectiveAckExtension](), payload: payload) Packet(header: h, eack: none[SelectiveAckExtension](), payload: payload)
proc resetPacket*(seqNr: uint16, sndConnectionId: uint16, ackNr: uint16): Packet = proc resetPacket*(
seqNr: uint16, sndConnectionId: uint16, ackNr: uint16): Packet =
let h = PacketHeaderV1( let h = PacketHeaderV1(
pType: ST_RESET, pType: ST_RESET,
version: protocolVersion, version: protocolVersion,
# data packets always have extension field set to 0 extension: 0'u8, # reset packets always have extension field set to 0
extension: 0'u8,
connectionId: sndConnectionId, connectionId: sndConnectionId,
timestamp: getMonoTimestamp().timestamp, timestamp: getMonoTimestamp().timestamp,
# reset packet informs remote about lack of state for given connection, therefore # reset packet informs remote about lack of state for given connection,
# we do not inform remote about its delay. # therefore the remote is not informed about its delay.
timestampDiff: 0, timestampDiff: 0,
wndSize: 0, wndSize: 0,
seqNr: seqNr, seqNr: seqNr,
@ -307,8 +309,7 @@ proc finPacket*(
let h = PacketHeaderV1( let h = PacketHeaderV1(
pType: ST_FIN, pType: ST_FIN,
version: protocolVersion, version: protocolVersion,
# fin packets always have extension field set to 0 extension: 0'u8, # fin packets always have extension field set to 0
extension: 0'u8,
connectionId: sndConnectionId, connectionId: sndConnectionId,
timestamp: getMonoTimestamp().timestamp, timestamp: getMonoTimestamp().timestamp,
timestampDiff: timestampDiff, timestampDiff: timestampDiff,

View File

@ -1,4 +1,4 @@
# Copyright (c) 2020-2021 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).
@ -14,9 +14,10 @@ import
suite "uTP Packet Encoding": suite "uTP Packet Encoding":
test "Encode/decode SYN packet": test "Encode/decode SYN packet":
let synPacket = synPacket(5, 10, 20) let
let encoded = encodePacket(synPacket) synPacket = synPacket(5, 10, 20)
let decoded = decodePacket(encoded) encoded = encodePacket(synPacket)
decoded = decodePacket(encoded)
check: check:
len(encoded) == 20 len(encoded) == 20
@ -24,13 +25,13 @@ suite "uTP Packet Encoding":
let synPacketDec = decoded.get() let synPacketDec = decoded.get()
check: check synPacketDec == synPacket
synPacketDec == synPacket
test "Encode/decode FIN packet": test "Encode/decode FIN packet":
let finPacket = finPacket(5, 10, 20, 30, 40) let
let encoded = encodePacket(finPacket) finPacket = finPacket(5, 10, 20, 30, 40)
let decoded = decodePacket(encoded) encoded = encodePacket(finPacket)
decoded = decodePacket(encoded)
check: check:
len(encoded) == 20 len(encoded) == 20
@ -38,13 +39,13 @@ suite "uTP Packet Encoding":
let finPacketDec = decoded.get() let finPacketDec = decoded.get()
check: check finPacketDec == finPacket
finPacketDec == finPacket
test "Encode/decode RESET packet": test "Encode/decode RESET packet":
let resetPacket = resetPacket(5, 10, 20) let
let encoded = encodePacket(resetPacket) resetPacket = resetPacket(5, 10, 20)
let decoded = decodePacket(encoded) encoded = encodePacket(resetPacket)
decoded = decodePacket(encoded)
check: check:
len(encoded) == 20 len(encoded) == 20
@ -52,13 +53,13 @@ suite "uTP Packet Encoding":
let resetPacketDec = decoded.get() let resetPacketDec = decoded.get()
check: check resetPacketDec == resetPacket
resetPacketDec == resetPacket
test "Encode/decode ACK packet: without extensions": test "Encode/decode ACK packet: without extensions":
let ackPacket = ackPacket(5, 10, 20, 30, 40) let
let encoded = encodePacket(ackPacket) ackPacket = ackPacket(5, 10, 20, 30, 40)
let decoded = decodePacket(encoded) encoded = encodePacket(ackPacket)
decoded = decodePacket(encoded)
check: check:
len(encoded) == 20 len(encoded) == 20
@ -66,14 +67,14 @@ suite "uTP Packet Encoding":
let ackPacketDec = decoded.get() let ackPacketDec = decoded.get()
check: check ackPacketDec == ackPacket
ackPacketDec == ackPacket
test "Encode/decode ACK packet: with extensions": test "Encode/decode ACK packet: with extensions":
let bitMask: array[4, byte] = [1'u8, 2, 3, 4] let
let ackPacket = ackPacket(5, 10, 20, 30, 40, some(bitMask)) bitMask: array[4, byte] = [1'u8, 2, 3, 4]
let encoded = encodePacket(ackPacket) ackPacket = ackPacket(5, 10, 20, 30, 40, some(bitMask))
let decoded = decodePacket(encoded) encoded = encodePacket(ackPacket)
decoded = decodePacket(encoded)
check: check:
len(encoded) == 26 len(encoded) == 26
@ -89,38 +90,29 @@ suite "uTP Packet Encoding":
let bitMask: array[4, byte] = [1'u8, 2, 3, 4] let bitMask: array[4, byte] = [1'u8, 2, 3, 4]
let ackPacket = ackPacket(5, 10, 20, 30, 40, some(bitMask)) let ackPacket = ackPacket(5, 10, 20, 30, 40, some(bitMask))
var encoded1 = encodePacket(ackPacket) block: # nextExtension to non zero
# change nextExtension to non zero var encoded = encodePacket(ackPacket)
encoded1[20] = 1 encoded[20] = 1
let err1 = decodePacket(encoded1) let err = decodePacket(encoded)
check: check err.isErr()
err1.isErr()
err1.error() == "Bad format of selective ack extension"
var encoded2 = encodePacket(ackPacket) block: # len of extension to value different than 4
# change len of extension to value different than 4 var encoded = encodePacket(ackPacket)
encoded2[21] = 7 encoded[21] = 7
let err2 = decodePacket(encoded2) let err = decodePacket(encoded)
check: check err.isErr()
err2.isErr()
err2.error() == "Bad format of selective ack extension"
var encoded3 = encodePacket(ackPacket) block: # delete last byte, now packet is too short
# delete last byte, now packet is to short var encoded = encodePacket(ackPacket)
encoded3.del(encoded3.high) encoded.del(encoded.high)
let err3 = decodePacket(encoded3) let err = decodePacket(encoded)
check err.isErr()
check: block: # change extension field to something other than 0 or 1
err3.isErr() var encoded = encodePacket(ackPacket)
err3.error() == "Packet too short for selective ack extension" encoded[1] = 2
let err = decodePacket(encoded)
var encoded4 = encodePacket(ackPacket) check: err.isErr()
# change change extension field to something other than 0 or 1
encoded4[1] = 2
let err4 = decodePacket(encoded4)
check:
err4.isErr()
err4.error() == "Invalid extension type"
test "Decode STATE packet": test "Decode STATE packet":
# Packet obtained by interaction with c reference implementation # Packet obtained by interaction with c reference implementation
@ -129,8 +121,7 @@ suite "uTP Packet Encoding":
0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x41, 0xA7, 0x00, 0x01] 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x41, 0xA7, 0x00, 0x01]
let decoded = decodePacket(pack) let decoded = decodePacket(pack)
check: check decoded.isOk()
decoded.isOk()
let packet = decoded.get() let packet = decoded.get()
@ -146,11 +137,12 @@ suite "uTP Packet Encoding":
packet.header.ackNr == 1 packet.header.ackNr == 1
test "Modify timestamp of encoded packet": test "Modify timestamp of encoded packet":
let synPacket = synPacket(5, 10, 20) let
let initialTimestamp = synPacket.header.timestamp synPacket = synPacket(5, 10, 20)
let initialAckNr = synPacket.header.ackNr initialTimestamp = synPacket.header.timestamp
let modifiedTimeStamp = initialTimestamp + 120324 initialAckNr = synPacket.header.ackNr
let modifiedAckNr = initialAckNr + 20 modifiedTimeStamp = initialTimestamp + 120324
modifiedAckNr = initialAckNr + 20
var encoded = encodePacket(synPacket) var encoded = encodePacket(synPacket)
modifyTimeStampAndAckNr(encoded, modifiedTimeStamp, modifiedAckNr) modifyTimeStampAndAckNr(encoded, modifiedTimeStamp, modifiedAckNr)