2018-04-30 17:40:04 +00:00
|
|
|
#
|
|
|
|
# Ethereum P2P
|
|
|
|
# (c) Copyright 2018
|
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
|
|
|
#
|
|
|
|
|
|
|
|
import uri, eth_keys, strutils, net
|
|
|
|
|
|
|
|
type
|
|
|
|
ENodeStatus* = enum
|
|
|
|
## ENode status codes
|
|
|
|
Success, ## Conversion operation succeed
|
|
|
|
IncorrectNodeId, ## Incorrect public key supplied
|
|
|
|
IncorrectScheme, ## Incorrect URI scheme supplied
|
|
|
|
IncorrectIP, ## Incorrect IP address supplied
|
|
|
|
IncorrectPort, ## Incorrect TCP port supplied
|
|
|
|
IncorrectDiscPort, ## Incorrect UDP discovery port supplied
|
|
|
|
IncorrectUri, ## Incorrect URI supplied
|
|
|
|
IncompleteENode ## Incomplete ENODE object
|
|
|
|
|
|
|
|
Address* = object
|
2018-05-10 19:02:12 +00:00
|
|
|
## Network address object
|
2018-04-30 17:40:04 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
ENodeException* = object of Exception
|
|
|
|
|
|
|
|
proc raiseENodeError(status: ENodeStatus) =
|
|
|
|
if status == IncorrectIP:
|
|
|
|
raise newException(ENodeException, "Incorrect IP address")
|
|
|
|
elif status == IncorrectPort:
|
|
|
|
raise newException(ENodeException, "Incorrect port number")
|
|
|
|
elif status == IncorrectDiscPort:
|
|
|
|
raise newException(ENodeException, "Incorrect discovery port number")
|
|
|
|
elif status == IncorrectUri:
|
|
|
|
raise newException(ENodeException, "Incorrect URI")
|
|
|
|
elif status == IncorrectScheme:
|
|
|
|
raise newException(ENodeException, "Incorrect scheme")
|
|
|
|
elif status == IncorrectNodeId:
|
|
|
|
raise newException(ENodeException, "Incorrect node id")
|
|
|
|
elif status == IncompleteENode:
|
|
|
|
raise newException(ENodeException, "Incomplete enode")
|
|
|
|
|
|
|
|
proc initENode*(e: string, node: var ENode): ENodeStatus =
|
|
|
|
## Initialize ENode ``node`` from URI string ``uri``.
|
|
|
|
var
|
|
|
|
uport: int = 0
|
|
|
|
tport: int = 0
|
|
|
|
uri: Uri = initUri()
|
|
|
|
data: string
|
|
|
|
|
|
|
|
if len(e) == 0:
|
|
|
|
return IncorrectUri
|
|
|
|
|
|
|
|
parseUri(e, uri)
|
|
|
|
|
|
|
|
if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode":
|
|
|
|
return IncorrectScheme
|
|
|
|
|
|
|
|
if len(uri.username) != 128:
|
|
|
|
return IncorrectNodeId
|
|
|
|
|
|
|
|
for i in uri.username:
|
|
|
|
if i notin {'A'..'F', 'a'..'f', '0'..'9'}:
|
|
|
|
return IncorrectNodeId
|
|
|
|
|
|
|
|
if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0:
|
|
|
|
return IncorrectUri
|
|
|
|
|
|
|
|
if len(uri.hostname) == 0:
|
|
|
|
return IncorrectIP
|
|
|
|
|
|
|
|
try:
|
|
|
|
if len(uri.port) == 0:
|
|
|
|
return IncorrectPort
|
|
|
|
tport = parseInt(uri.port)
|
|
|
|
if tport <= 0 or tport > 65535:
|
|
|
|
return IncorrectPort
|
|
|
|
except:
|
|
|
|
return IncorrectPort
|
|
|
|
|
|
|
|
if len(uri.query) > 0:
|
|
|
|
if not uri.query.toLowerAscii().startsWith("discport="):
|
|
|
|
return IncorrectDiscPort
|
|
|
|
try:
|
|
|
|
uport = parseInt(uri.query[9..^1])
|
|
|
|
if uport <= 0 or uport > 65535:
|
|
|
|
return IncorrectDiscPort
|
|
|
|
except:
|
|
|
|
return IncorrectDiscPort
|
|
|
|
else:
|
|
|
|
uport = tport
|
|
|
|
|
|
|
|
try:
|
|
|
|
data = parseHexStr(uri.username)
|
|
|
|
if recoverPublicKey(cast[seq[byte]](data),
|
|
|
|
node.pubkey) != EthKeysStatus.Success:
|
|
|
|
return IncorrectNodeId
|
|
|
|
except:
|
|
|
|
return IncorrectNodeId
|
|
|
|
|
|
|
|
try:
|
|
|
|
node.address.ip = parseIpAddress(uri.hostname)
|
|
|
|
except:
|
|
|
|
zeroMem(addr node.pubkey, KeyLength * 2)
|
|
|
|
return IncorrectIP
|
|
|
|
|
|
|
|
node.address.tcpPort = Port(tport)
|
|
|
|
node.address.udpPort = Port(uport)
|
|
|
|
result = Success
|
|
|
|
|
|
|
|
proc initENode*(uri: string): ENode {.inline.} =
|
|
|
|
## Returns ENode object from URI string ``uri``.
|
|
|
|
let res = initENode(uri, result)
|
|
|
|
if res != Success:
|
|
|
|
raiseENodeError(res)
|
|
|
|
|
2018-05-10 19:02:12 +00:00
|
|
|
proc initENode*(pubkey: PublicKey, address: Address): ENode {.inline.} =
|
2018-04-30 21:45:06 +00:00
|
|
|
## Create ENode object from public key ``pubkey`` and ``address``.
|
|
|
|
result.pubkey = pubkey
|
|
|
|
result.address = address
|
|
|
|
|
2018-04-30 17:40:04 +00:00
|
|
|
proc isCorrect*(n: ENode): bool =
|
|
|
|
## Returns ``true`` if ENode ``n`` is properly filled.
|
|
|
|
result = false
|
|
|
|
for i in n.pubkey.data:
|
|
|
|
if i != 0x00'u8:
|
|
|
|
result = true
|
|
|
|
break
|
|
|
|
|
|
|
|
proc `$`*(n: ENode): string =
|
|
|
|
## Returns string representation of ENode.
|
|
|
|
var ipaddr: string
|
|
|
|
if not isCorrect(n):
|
|
|
|
raiseENodeError(IncompleteENode)
|
|
|
|
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)
|
2018-05-10 19:02:12 +00:00
|
|
|
if uint16(n.address.tcpPort) != 0:
|
|
|
|
result.add(":")
|
|
|
|
result.add($int(n.address.tcpPort))
|
2018-04-30 17:40:04 +00:00
|
|
|
if uint16(n.address.udpPort) != uint16(n.address.tcpPort):
|
|
|
|
result.add("?")
|
|
|
|
result.add("discport=")
|
|
|
|
result.add($int(n.address.udpPort))
|