Add ENR Record.init and update call for dual stack

This commit is contained in:
kdeme 2024-10-11 17:06:07 +02:00
parent 470baf82bd
commit 45b311fe9c
No known key found for this signature in database
GPG Key ID: 4E8DD21420AF43F5
1 changed files with 115 additions and 1 deletions

View File

@ -243,7 +243,9 @@ func init*(
extraFields: openArray[FieldPair] = []): extraFields: openArray[FieldPair] = []):
EnrResult[T] = EnrResult[T] =
## Initialize a `Record` with given sequence number, private key, optional ## Initialize a `Record` with given sequence number, private key, optional
## ip address, tcp port, udp port, and optional custom k:v pairs. ## IP address, TCP port, UDP port, and optional custom k:v pairs.
##
## The IP address can be an IPv4 or IPv6 address.
## ##
## Can fail in case the record exceeds the `maxEnrSize`. ## Can fail in case the record exceeds the `maxEnrSize`.
doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs") doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs")
@ -254,6 +256,51 @@ func init*(
fields.insert extraFields fields.insert extraFields
makeEnrAux(seqNum, "v4", pk, fields) makeEnrAux(seqNum, "v4", pk, fields)
func init*(
T: type Record,
seqNum: uint64, pk: PrivateKey,
ip4: array[4, byte],
ip6: array[16, byte],
tcp4Port: Opt[Port] = Opt.none(Port),
udp4Port: Opt[Port] = Opt.none(Port),
tcp6Port: Opt[Port] = Opt.none(Port),
udp6Port: Opt[Port] = Opt.none(Port),
extraFields: openArray[FieldPair] = []
): EnrResult[T] =
## Initialize a `Record` with given sequence number, private key, IPv4 and IPv6
## address, optional TCP port and UDP port for both IPv4 and IPV6, and
## optional custom k:v pairs.
##
## This function is to be used when running in IPv4 and IPv6 dual stack mode.
## The tcp6 and udp6 fields will only be set if they differ from the tcp and udp
## fields.
##
## Can fail in case the record exceeds the `maxEnrSize`.
doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs")
var fields = newSeq[FieldPair]()
fields.insert(("ip", ip4.toField))
fields.insert(("ip6", ip6.toField))
if tcp4Port.isSome():
fields.insert(("tcp", tcp4Port.value().uint16.toField))
if udp4Port.isSome():
fields.insert(("udp", udp4Port.value().uint16.toField))
# From the ENR specification:
# "Declaring the same port number in both tcp, tcp6 or udp, udp6 should be avoided
# but doesn't render the record invalid."
if tcp6Port.isSome():
if tcp6Port != tcp4Port:
fields.insert(("tcp6", tcp6Port.value().uint16.toField))
if udp6Port.isSome():
if udp6Port != udp4Port:
fields.insert(("udp6", udp6Port.value().uint16.toField))
fields.add extraFields
makeEnrAux(seqNum, "v4", pk, extraFields)
func getField(r: Record, name: string, field: var Field): bool = func getField(r: Record, name: string, field: var Field): bool =
# It might be more correct to do binary search, # It might be more correct to do binary search,
# as the fields are sorted, but it's unlikely to # as the fields are sorted, but it's unlikely to
@ -357,6 +404,73 @@ func update*(
ok() ok()
func update*(
record: var Record,
pk: PrivateKey,
ip4: array[4, byte],
ip6: array[16, byte],
tcp4Port: Opt[Port] = Opt.none(Port),
udp4Port: Opt[Port] = Opt.none(Port),
tcp6Port: Opt[Port] = Opt.none(Port),
udp6Port: Opt[Port] = Opt.none(Port),
extraFields: openArray[FieldPair] = []):
EnrResult[void] =
## Update a `Record` with given IPv4 and IPv6 address, optional TCP port and
## UDP port for both IPv4 and IPV6, and optional custom k:v pairs.
##
## This function is to be used when running in IPv4 and IPv6 dual stack mode.
## The tcp6 and udp6 fields will only be set if they differ from the tcp and udp
## fields.
##
## If none of the k:v pairs are changed, the sequence number of the `Record`
## will still be incremented and a new signature will be applied.
##
## Providing an `Opt.none` for `tcp4Port`/`udp4Port`/`tcp6Port`/`udp6Port`/
## will leave the corresponding field untouched.
##
## Can fail in case of wrong `PrivateKey`, if the size of the resulting record
## exceeds `maxEnrSize` or if maximum sequence number is reached. The `Record`
## will not be altered in these cases.
# TODO: deprecate this call and have individual functions for updating?
doAssert(not hasPredefinedKey(extraFields), "Predefined key in custom pairs")
var r = record
let pubkey = r.get(PublicKey)
if pubkey.isNone() or pubkey.get() != pk.toPublicKey():
return err("Public key does not correspond with given private key")
var fields = newSeq[FieldPair]()
fields.insert(("ip", ip4.toField))
fields.insert(("ip6", ip6.toField))
if tcp4Port.isSome():
fields.insert(("tcp", tcp4Port.value().uint16.toField))
if udp4Port.isSome():
fields.insert(("udp", udp4Port.value().uint16.toField))
# From the ENR specification:
# "Declaring the same port number in both tcp, tcp6 or udp, udp6 should be avoided
# but doesn't render the record invalid."
if tcp6Port.isSome():
if tcp6Port != tcp4Port:
fields.insert(("tcp6", tcp6Port.value().uint16.toField))
if udp6Port.isSome():
if udp6Port != udp4Port:
fields.insert(("udp6", udp6Port.value().uint16.toField))
r.pairs.insert extraFields
if r.seqNum == high(type r.seqNum): # highly unlikely
return err("Maximum sequence number reached")
r.seqNum.inc()
r.raw = ? makeEnrRaw(r.seqNum, pk, r.pairs)
record = r
ok()
func tryGet*(r: Record, key: string, T: type): Opt[T] = func tryGet*(r: Record, key: string, T: type): Opt[T] =
## Get the value from the provided key. ## Get the value from the provided key.
## Return `none` if the key does not exist or if the value is invalid ## Return `none` if the key does not exist or if the value is invalid