2019-02-05 15:40:29 +00:00
|
|
|
#
|
|
|
|
# Ethereum P2P
|
2023-05-10 13:50:04 +00:00
|
|
|
# (c) Copyright 2018-2023
|
2019-02-05 15:40:29 +00:00
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
|
|
|
#
|
|
|
|
|
2023-05-10 13:50:04 +00:00
|
|
|
{.push raises: [].}
|
2020-04-06 16:24:15 +00:00
|
|
|
|
2021-04-06 11:33:24 +00:00
|
|
|
import
|
|
|
|
std/[uri, strutils, net],
|
2023-04-18 11:51:02 +00:00
|
|
|
pkg/chronicles,
|
2021-04-06 11:33:24 +00:00
|
|
|
../keys
|
2019-02-05 15:40:29 +00:00
|
|
|
|
2020-04-02 12:40:29 +00:00
|
|
|
export keys
|
|
|
|
|
2019-02-05 15:40:29 +00:00
|
|
|
type
|
2020-04-06 16:24:15 +00:00
|
|
|
ENodeError* = enum
|
2019-02-05 15:40:29 +00:00
|
|
|
## ENode status codes
|
2020-04-06 16:24:15 +00:00
|
|
|
IncorrectNodeId = "enode: incorrect public key"
|
|
|
|
IncorrectScheme = "enode: incorrect URI scheme"
|
|
|
|
IncorrectIP = "enode: incorrect IP address"
|
|
|
|
IncorrectPort = "enode: incorrect TCP port"
|
|
|
|
IncorrectDiscPort = "enode: incorrect UDP discovery port"
|
|
|
|
IncorrectUri = "enode: incorrect URI"
|
|
|
|
IncompleteENode = "enode: incomplete ENODE object"
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
Address* = object
|
|
|
|
## Network address object
|
|
|
|
ip*: IpAddress ## IPv4/IPv6 address
|
|
|
|
udpPort*: Port ## UDP discovery port number
|
|
|
|
tcpPort*: Port ## TCP port number
|
|
|
|
|
|
|
|
ENode* = object
|
|
|
|
## ENode object
|
|
|
|
pubkey*: PublicKey ## Node public key
|
|
|
|
address*: Address ## Node address
|
|
|
|
|
2020-04-06 16:24:15 +00:00
|
|
|
ENodeResult*[T] = Result[T, ENodeError]
|
|
|
|
|
|
|
|
proc mapErrTo[T, E](r: Result[T, E], v: static ENodeError): ENodeResult[T] =
|
|
|
|
r.mapErr(proc (e: E): ENodeError = v)
|
|
|
|
|
|
|
|
proc fromString*(T: type ENode, e: string): ENodeResult[ENode] =
|
2019-02-05 15:40:29 +00:00
|
|
|
## Initialize ENode ``node`` from URI string ``uri``.
|
|
|
|
var
|
|
|
|
uport: int = 0
|
|
|
|
tport: int = 0
|
|
|
|
uri: Uri = initUri()
|
|
|
|
|
|
|
|
if len(e) == 0:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectUri)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
parseUri(e, uri)
|
|
|
|
|
|
|
|
if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode":
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectScheme)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
if len(uri.username) != 128:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectNodeId)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
for i in uri.username:
|
|
|
|
if i notin {'A'..'F', 'a'..'f', '0'..'9'}:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectNodeId)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectUri)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
if len(uri.hostname) == 0:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectIP)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
if len(uri.port) == 0:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectPort)
|
2019-02-05 15:40:29 +00:00
|
|
|
tport = parseInt(uri.port)
|
|
|
|
if tport <= 0 or tport > 65535:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectPort)
|
2019-12-04 11:34:37 +00:00
|
|
|
except ValueError:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectPort)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
if len(uri.query) > 0:
|
|
|
|
if not uri.query.toLowerAscii().startsWith("discport="):
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectDiscPort)
|
2019-02-05 15:40:29 +00:00
|
|
|
try:
|
|
|
|
uport = parseInt(uri.query[9..^1])
|
|
|
|
if uport <= 0 or uport > 65535:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectDiscPort)
|
2019-12-04 11:34:37 +00:00
|
|
|
except ValueError:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectDiscPort)
|
2019-02-05 15:40:29 +00:00
|
|
|
else:
|
|
|
|
uport = tport
|
|
|
|
|
2020-04-06 16:24:15 +00:00
|
|
|
var ip: IpAddress
|
2019-02-05 15:40:29 +00:00
|
|
|
try:
|
2020-04-06 16:24:15 +00:00
|
|
|
ip = parseIpAddress(uri.hostname)
|
2019-12-04 11:34:37 +00:00
|
|
|
except ValueError:
|
2020-04-06 16:24:15 +00:00
|
|
|
return err(IncorrectIP)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
2020-04-06 16:24:15 +00:00
|
|
|
let pubkey = ? PublicKey.fromHex(uri.username).mapErrTo(IncorrectNodeId)
|
2019-02-05 15:40:29 +00:00
|
|
|
|
2020-04-06 16:24:15 +00:00
|
|
|
ok(ENode(
|
|
|
|
pubkey: pubkey,
|
|
|
|
address: Address(
|
|
|
|
ip: ip,
|
|
|
|
tcpPort: Port(tport),
|
|
|
|
udpPort: Port(uport)
|
|
|
|
)
|
|
|
|
))
|
2019-02-05 15:40:29 +00:00
|
|
|
|
|
|
|
proc `$`*(n: ENode): string =
|
|
|
|
## Returns string representation of ENode.
|
|
|
|
var ipaddr: string
|
|
|
|
if n.address.ip.family == IpAddressFamily.IPv4:
|
|
|
|
ipaddr = $(n.address.ip)
|
|
|
|
else:
|
|
|
|
ipaddr = "[" & $(n.address.ip) & "]"
|
|
|
|
result = newString(0)
|
|
|
|
result.add("enode://")
|
|
|
|
result.add($n.pubkey)
|
|
|
|
result.add("@")
|
|
|
|
result.add(ipaddr)
|
|
|
|
if uint16(n.address.tcpPort) != 0:
|
|
|
|
result.add(":")
|
|
|
|
result.add($int(n.address.tcpPort))
|
|
|
|
if uint16(n.address.udpPort) != uint16(n.address.tcpPort):
|
|
|
|
result.add("?")
|
|
|
|
result.add("discport=")
|
|
|
|
result.add($int(n.address.udpPort))
|
2020-02-27 21:36:42 +00:00
|
|
|
|
|
|
|
proc `$`*(a: Address): string =
|
|
|
|
result.add($a.ip)
|
|
|
|
result.add(":" & $a.udpPort)
|
|
|
|
result.add(":" & $a.tcpPort)
|
2023-04-18 11:51:02 +00:00
|
|
|
|
|
|
|
chronicles.formatIt(Address): $it
|
|
|
|
chronicles.formatIt(ENode): $it
|