2021-12-06 19:51:37 +00:00
|
|
|
|
## Collection of utilities related to Waku's use of EIP-778 ENR
|
|
|
|
|
## Implemented according to the specified Waku v2 ENR usage
|
|
|
|
|
## More at https://rfc.vac.dev/spec/31/
|
|
|
|
|
|
2023-03-07 09:52:12 +00:00
|
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
else:
|
|
|
|
|
{.push raises: [].}
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
|
|
|
|
import
|
2023-03-21 16:27:51 +00:00
|
|
|
|
std/[options, bitops, sequtils],
|
2023-03-07 09:52:12 +00:00
|
|
|
|
stew/[endians2, results],
|
|
|
|
|
stew/shims/net,
|
2021-12-06 19:51:37 +00:00
|
|
|
|
eth/keys,
|
|
|
|
|
libp2p/[multiaddress, multicodec],
|
2023-03-07 09:52:12 +00:00
|
|
|
|
libp2p/crypto/crypto
|
2023-03-10 10:49:41 +00:00
|
|
|
|
import
|
|
|
|
|
../../common/enr
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
|
|
|
|
export enr, crypto, multiaddress, net
|
|
|
|
|
|
|
|
|
|
const
|
2023-03-08 14:44:10 +00:00
|
|
|
|
MultiaddrEnrField* = "multiaddrs"
|
|
|
|
|
CapabilitiesEnrField* = "waku2"
|
|
|
|
|
|
|
|
|
|
|
2023-03-10 10:49:41 +00:00
|
|
|
|
## Node capabilities
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
|
|
|
|
type
|
2023-03-10 10:49:41 +00:00
|
|
|
|
## 8-bit flag field to indicate Waku node capabilities.
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## Only the 4 LSBs are currently defined according
|
|
|
|
|
## to RFC31 (https://rfc.vac.dev/spec/31/).
|
2023-03-08 14:44:10 +00:00
|
|
|
|
CapabilitiesBitfield* = distinct uint8
|
2022-11-10 09:29:34 +00:00
|
|
|
|
|
|
|
|
|
## See: https://rfc.vac.dev/spec/31/#waku2-enr-key
|
|
|
|
|
## each enum numbers maps to a bit (where 0 is the LSB)
|
2023-03-22 15:01:02 +00:00
|
|
|
|
Capabilities*{.pure.} = enum
|
2022-11-10 09:29:34 +00:00
|
|
|
|
Relay = 0,
|
|
|
|
|
Store = 1,
|
|
|
|
|
Filter = 2,
|
2023-03-08 14:44:10 +00:00
|
|
|
|
Lightpush = 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func init*(T: type CapabilitiesBitfield, lightpush, filter, store, relay: bool): T =
|
|
|
|
|
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
|
|
|
|
|
var bitfield: uint8
|
|
|
|
|
if relay: bitfield.setBit(0)
|
|
|
|
|
if store: bitfield.setBit(1)
|
|
|
|
|
if filter: bitfield.setBit(2)
|
|
|
|
|
if lightpush: bitfield.setBit(3)
|
|
|
|
|
CapabilitiesBitfield(bitfield)
|
|
|
|
|
|
|
|
|
|
func init*(T: type CapabilitiesBitfield, caps: varargs[Capabilities]): T =
|
|
|
|
|
## Creates an waku2 ENR flag bit field according to RFC 31 (https://rfc.vac.dev/spec/31/)
|
|
|
|
|
var bitfield: uint8
|
|
|
|
|
for cap in caps:
|
|
|
|
|
bitfield.setBit(ord(cap))
|
|
|
|
|
CapabilitiesBitfield(bitfield)
|
|
|
|
|
|
|
|
|
|
converter toCapabilitiesBitfield*(field: uint8): CapabilitiesBitfield =
|
|
|
|
|
CapabilitiesBitfield(field)
|
|
|
|
|
|
|
|
|
|
proc supportsCapability*(bitfield: CapabilitiesBitfield, cap: Capabilities): bool =
|
|
|
|
|
testBit(bitfield.uint8, ord(cap))
|
|
|
|
|
|
|
|
|
|
func toCapabilities*(bitfield: CapabilitiesBitfield): seq[Capabilities] =
|
|
|
|
|
toSeq(Capabilities.low..Capabilities.high).filterIt(supportsCapability(bitfield, it))
|
|
|
|
|
|
|
|
|
|
|
2023-03-10 10:49:41 +00:00
|
|
|
|
# ENR builder extension
|
|
|
|
|
|
|
|
|
|
proc withWakuCapabilities*(builder: var EnrBuilder, caps: CapabilitiesBitfield) =
|
|
|
|
|
builder.addFieldPair(CapabilitiesEnrField, @[caps.uint8])
|
|
|
|
|
|
|
|
|
|
proc withWakuCapabilities*(builder: var EnrBuilder, caps: varargs[Capabilities]) =
|
|
|
|
|
withWakuCapabilities(builder, CapabilitiesBitfield.init(caps))
|
|
|
|
|
|
|
|
|
|
proc withWakuCapabilities*(builder: var EnrBuilder, caps: openArray[Capabilities]) =
|
|
|
|
|
withWakuCapabilities(builder, CapabilitiesBitfield.init(@caps))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ENR record accessors (e.g., Record, TypedRecord, etc.)
|
2023-03-08 14:44:10 +00:00
|
|
|
|
|
2023-03-22 15:01:02 +00:00
|
|
|
|
func waku2*(record: TypedRecord): Option[CapabilitiesBitfield] =
|
|
|
|
|
let field = record.tryGet(CapabilitiesEnrField, seq[uint8])
|
|
|
|
|
if field.isNone():
|
|
|
|
|
return none(CapabilitiesBitfield)
|
2023-03-08 14:44:10 +00:00
|
|
|
|
|
2023-03-22 15:01:02 +00:00
|
|
|
|
some(CapabilitiesBitfield(field.get()[0]))
|
2023-03-08 14:44:10 +00:00
|
|
|
|
|
|
|
|
|
proc supportsCapability*(r: Record, cap: Capabilities): bool =
|
2023-03-22 15:01:02 +00:00
|
|
|
|
let recordRes = r.toTyped()
|
|
|
|
|
if recordRes.isErr():
|
2023-03-08 14:44:10 +00:00
|
|
|
|
return false
|
|
|
|
|
|
2023-03-22 15:01:02 +00:00
|
|
|
|
let bitfieldOpt = recordRes.value.waku2
|
|
|
|
|
if bitfieldOpt.isNone():
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
let bitfield = bitfieldOpt.get()
|
|
|
|
|
bitfield.supportsCapability(cap)
|
2023-03-08 14:44:10 +00:00
|
|
|
|
|
|
|
|
|
proc getCapabilities*(r: Record): seq[Capabilities] =
|
2023-03-22 15:01:02 +00:00
|
|
|
|
let recordRes = r.toTyped()
|
|
|
|
|
if recordRes.isErr():
|
|
|
|
|
return @[]
|
|
|
|
|
|
|
|
|
|
let bitfieldOpt = recordRes.value.waku2
|
|
|
|
|
if bitfieldOpt.isNone():
|
2023-03-08 14:44:10 +00:00
|
|
|
|
return @[]
|
|
|
|
|
|
2023-03-22 15:01:02 +00:00
|
|
|
|
let bitfield = bitfieldOpt.get()
|
|
|
|
|
bitfield.toCapabilities()
|
2023-03-08 14:44:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Multiaddress
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
func encodeMultiaddrs*(multiaddrs: seq[MultiAddress]): seq[byte] =
|
|
|
|
|
var buffer = newSeq[byte]()
|
2021-12-06 19:51:37 +00:00
|
|
|
|
for multiaddr in multiaddrs:
|
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
let
|
|
|
|
|
raw = multiaddr.data.buffer # binary encoded multiaddr
|
|
|
|
|
size = raw.len.uint16.toBytes(Endianness.bigEndian) # size as Big Endian unsigned 16-bit integer
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
buffer.add(concat(@size, raw))
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
buffer
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
|
|
|
|
func readBytes(rawBytes: seq[byte], numBytes: int, pos: var int = 0): Result[seq[byte], cstring] =
|
2023-02-07 13:06:50 +00:00
|
|
|
|
## Attempts to read `numBytes` from a sequence, from
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## position `pos`. Returns the requested slice or
|
|
|
|
|
## an error if `rawBytes` boundary is exceeded.
|
2023-02-07 13:06:50 +00:00
|
|
|
|
##
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## If successful, `pos` is advanced by `numBytes`
|
|
|
|
|
if rawBytes[pos..^1].len() < numBytes:
|
2023-03-30 07:35:13 +00:00
|
|
|
|
return err("insufficient bytes")
|
2023-02-07 13:06:50 +00:00
|
|
|
|
|
2021-12-06 19:51:37 +00:00
|
|
|
|
let slicedSeq = rawBytes[pos..<pos+numBytes]
|
|
|
|
|
pos += numBytes
|
|
|
|
|
|
|
|
|
|
return ok(slicedSeq)
|
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
func decodeMultiaddrs(buffer: seq[byte]): EnrResult[seq[MultiAddress]] =
|
2021-12-06 19:51:37 +00:00
|
|
|
|
## Parses a `multiaddrs` ENR field according to
|
|
|
|
|
## https://rfc.vac.dev/spec/31/
|
|
|
|
|
var multiaddrs: seq[MultiAddress]
|
|
|
|
|
|
|
|
|
|
var pos = 0
|
2023-03-30 07:35:13 +00:00
|
|
|
|
while pos < buffer.len():
|
|
|
|
|
let addrLenRaw = ? readBytes(buffer, 2, pos)
|
|
|
|
|
let addrLen = uint16.fromBytesBE(addrLenRaw)
|
|
|
|
|
if addrLen == 0:
|
2021-12-06 19:51:37 +00:00
|
|
|
|
# Ensure pos always advances and we don't get stuck in infinite loop
|
2023-03-30 07:35:13 +00:00
|
|
|
|
return err("malformed multiaddr field: invalid length")
|
|
|
|
|
|
|
|
|
|
let addrRaw = ? readBytes(buffer, addrLen.int, pos)
|
|
|
|
|
let address = MultiAddress.init(addrRaw).get()
|
|
|
|
|
|
|
|
|
|
multiaddrs.add(address)
|
|
|
|
|
|
|
|
|
|
return ok(multiaddrs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ENR builder extension
|
|
|
|
|
func stripPeerId(multiaddr: MultiAddress): MultiAddress =
|
|
|
|
|
if not multiaddr.contains(multiCodec("p2p")).get():
|
|
|
|
|
return multiaddr
|
|
|
|
|
|
|
|
|
|
var cleanAddr = MultiAddress.init()
|
|
|
|
|
for item in multiaddr.items:
|
|
|
|
|
if item.value.protoName().get() != "p2p":
|
|
|
|
|
# Add all parts except p2p peerId
|
|
|
|
|
discard cleanAddr.append(item.value)
|
|
|
|
|
|
|
|
|
|
return cleanAddr
|
|
|
|
|
|
|
|
|
|
func withMultiaddrs*(builder: var EnrBuilder, multiaddrs: seq[MultiAddress]) =
|
|
|
|
|
let multiaddrs = multiaddrs.map(stripPeerId)
|
|
|
|
|
let value = encodeMultiaddrs(multiaddrs)
|
|
|
|
|
builder.addFieldPair(MultiaddrEnrField, value)
|
|
|
|
|
|
|
|
|
|
func withMultiaddrs*(builder: var EnrBuilder, multiaddrs: varargs[MultiAddress]) =
|
|
|
|
|
withMultiaddrs(builder, @multiaddrs)
|
|
|
|
|
|
|
|
|
|
# ENR record accessors (e.g., Record, TypedRecord, etc.)
|
|
|
|
|
|
|
|
|
|
func multiaddrs*(record: TypedRecord): Option[seq[MultiAddress]] =
|
|
|
|
|
let field = record.tryGet(MultiaddrEnrField, seq[byte])
|
|
|
|
|
if field.isNone():
|
|
|
|
|
return none(seq[MultiAddress])
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
let decodeRes = decodeMultiaddrs(field.get())
|
|
|
|
|
if decodeRes.isErr():
|
|
|
|
|
return none(seq[MultiAddress])
|
2021-12-06 19:51:37 +00:00
|
|
|
|
|
2023-03-30 07:35:13 +00:00
|
|
|
|
some(decodeRes.value)
|