# # Ethereum P2P # (c) Copyright 2018-2023 # Status Research & Development GmbH # # Licensed under either of # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) # {.push raises: [].} import std/[uri, strutils, net], pkg/chronicles, ../common/keys export keys type ENodeError* = enum ## ENode status codes 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" 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 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] = ## Initialize ENode ``node`` from URI string ``uri``. var uport: int = 0 tport: int = 0 uri: Uri = initUri() if len(e) == 0: return err(IncorrectUri) parseUri(e, uri) if len(uri.scheme) == 0 or uri.scheme.toLowerAscii() != "enode": return err(IncorrectScheme) if len(uri.username) != 128: return err(IncorrectNodeId) for i in uri.username: if i notin {'A'..'F', 'a'..'f', '0'..'9'}: return err(IncorrectNodeId) if len(uri.password) != 0 or len(uri.path) != 0 or len(uri.anchor) != 0: return err(IncorrectUri) if len(uri.hostname) == 0: return err(IncorrectIP) try: if len(uri.port) == 0: return err(IncorrectPort) tport = parseInt(uri.port) if tport <= 0 or tport > 65535: return err(IncorrectPort) except ValueError: return err(IncorrectPort) if len(uri.query) > 0: if not uri.query.toLowerAscii().startsWith("discport="): return err(IncorrectDiscPort) try: uport = parseInt(uri.query[9..^1]) if uport <= 0 or uport > 65535: return err(IncorrectDiscPort) except ValueError: return err(IncorrectDiscPort) else: uport = tport var ip: IpAddress try: ip = parseIpAddress(uri.hostname) except ValueError: return err(IncorrectIP) let pubkey = ? PublicKey.fromHex(uri.username).mapErrTo(IncorrectNodeId) ok(ENode( pubkey: pubkey, address: Address( ip: ip, tcpPort: Port(tport), udpPort: Port(uport) ) )) 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)) proc `$`*(a: Address): string = result.add($a.ip) result.add(":" & $a.udpPort) result.add(":" & $a.tcpPort) chronicles.formatIt(Address): $it chronicles.formatIt(ENode): $it