Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discv5 factorize #480

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
65 changes: 1 addition & 64 deletions eth/p2p/discoveryv5/encoding.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import
std/[tables, options, hashes, net],
nimcrypto, stint, chronicles, bearssl, stew/[results, byteutils], metrics,
".."/../[rlp, keys],
"."/[messages, node, enr, hkdf, sessions]
"."/[messages, messages_encoding, node, enr, hkdf, sessions]

from stew/objects import checkedEnumAssign

Expand Down Expand Up @@ -367,50 +367,6 @@ proc decodeHeader*(id: NodeId, iv, maskedHeader: openArray[byte]):
ok((StaticHeader(authdataSize: authdataSize, flag: flag, nonce: nonce),
staticHeader & authdata))

proc decodeMessage*(body: openArray[byte]): DecodeResult[Message] =
## Decodes to the specific `Message` type.
if body.len < 1:
return err("No message data")

var kind: MessageKind
if not checkedEnumAssign(kind, body[0]):
return err("Invalid message type")

var message = Message(kind: kind)
var rlp = rlpFromBytes(body.toOpenArray(1, body.high))
if rlp.enterList:
try:
message.reqId = rlp.read(RequestId)
except RlpError, ValueError:
return err("Invalid request-id")

proc decode[T](rlp: var Rlp, v: var T)
{.nimcall, raises:[RlpError, ValueError, Defect].} =
for k, v in v.fieldPairs:
v = rlp.read(typeof(v))

try:
case kind
of unused: return err("Invalid message type")
of ping: rlp.decode(message.ping)
of pong: rlp.decode(message.pong)
of findNode: rlp.decode(message.findNode)
of nodes: rlp.decode(message.nodes)
of talkReq: rlp.decode(message.talkReq)
of talkResp: rlp.decode(message.talkResp)
of regTopic, ticket, regConfirmation, topicQuery:
# We just pass the empty type of this message without attempting to
# decode, so that the protocol knows what was received.
# But we ignore the message as per specification as "the content and
# semantics of this message are not final".
discard
except RlpError, ValueError:
return err("Invalid message encoding")

ok(message)
else:
err("Invalid message encoding: no rlp list")

proc decodeMessagePacket(c: var Codec, fromAddr: Address, nonce: AESGCMNonce,
iv, header, ct: openArray[byte]): DecodeResult[Packet] =
# We now know the exact size that the header should be
Expand Down Expand Up @@ -602,22 +558,3 @@ proc decodePacket*(c: var Codec, fromAddr: Address, input: openArray[byte]):
return decodeHandshakePacket(c, fromAddr, staticHeader.nonce,
input.toOpenArray(0, ivSize - 1), header,
input.toOpenArray(ivSize + header.len, input.high))

proc init*(T: type RequestId, rng: var BrHmacDrbgContext): T =
var reqId = RequestId(id: newSeq[byte](8)) # RequestId must be <= 8 bytes
brHmacDrbgGenerate(rng, reqId.id)
reqId

proc numFields(T: typedesc): int =
for k, v in fieldPairs(default(T)): inc result

proc encodeMessage*[T: SomeMessage](p: T, reqId: RequestId): seq[byte] =
result = newSeqOfCap[byte](64)
result.add(messageKind(T).ord)

const sz = numFields(T)
var writer = initRlpList(sz + 1)
writer.append(reqId)
for k, v in fieldPairs(p):
writer.append(v)
result.add(writer.finish())
2 changes: 1 addition & 1 deletion eth/p2p/discoveryv5/enr.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import
stew/shims/net, stew/[base64, results], nimcrypto,
".."/../[rlp, keys]

export options, results, keys
export options, results

const
maxEnrSize = 300 ## Maximum size of an encoded node record, in bytes.
Expand Down
46 changes: 7 additions & 39 deletions eth/p2p/discoveryv5/messages.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

import
std/[hashes, net],
stew/arrayops,
../../rlp, ./enr
".."/../[keys],
./enr

type
MessageKind* = enum
Expand Down Expand Up @@ -103,42 +103,10 @@ template messageKind*(T: typedesc[SomeMessage]): MessageKind =
elif T is TalkReqMessage: talkReq
elif T is TalkRespMessage: talkResp

proc read*(rlp: var Rlp, T: type RequestId): T
{.raises: [ValueError, RlpError, Defect].} =
mixin read
var reqId: RequestId
reqId.id = rlp.toBytes()
if reqId.id.len > 8:
raise newException(ValueError, "RequestId is > 8 bytes")
rlp.skipElem()

reqId

proc append*(writer: var RlpWriter, value: RequestId) =
writer.append(value.id)

proc read*(rlp: var Rlp, T: type IpAddress): T
{.raises: [RlpError, Defect].} =
let ipBytes = rlp.toBytes()
rlp.skipElem()

if ipBytes.len == 4:
var ip: array[4, byte]
discard copyFrom(ip, ipBytes)
IpAddress(family: IPv4, address_v4: ip)
elif ipBytes.len == 16:
var ip: array[16, byte]
discard copyFrom(ip, ipBytes)
IpAddress(family: IPv6, address_v6: ip)
else:
raise newException(RlpTypeMismatch,
"Amount of bytes for IP address is different from 4 or 16")

proc append*(writer: var RlpWriter, ip: IpAddress) =
case ip.family:
of IpAddressFamily.IPv4:
writer.append(ip.address_v4)
of IpAddressFamily.IPv6: writer.append(ip.address_v6)

proc hash*(reqId: RequestId): Hash =
hash(reqId.id)

proc init*(T: type RequestId, rng: var BrHmacDrbgContext): T =
var reqId = RequestId(id: newSeq[byte](8)) # RequestId must be <= 8 bytes
brHmacDrbgGenerate(rng, reqId.id)
reqId
116 changes: 116 additions & 0 deletions eth/p2p/discoveryv5/messages_encoding.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# nim-eth - Node Discovery Protocol v5
# Copyright (c) 2020-2022 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
#
## Discovery v5 packet encoding as specified at
## https://github.com/ethereum/devp2p/blob/master/discv5/discv5-wire.md#packet-encoding

import
std/net,
stew/arrayops,
".."/../[rlp],
"."/[messages, enr]

from stew/objects import checkedEnumAssign

type
DecodeResult*[T] = Result[T, cstring]

proc read*(rlp: var Rlp, T: type RequestId): T
{.raises: [ValueError, RlpError, Defect].} =
mixin read
var reqId: RequestId
reqId.id = rlp.toBytes()
if reqId.id.len > 8:
raise newException(ValueError, "RequestId is > 8 bytes")
rlp.skipElem()

reqId

proc append*(writer: var RlpWriter, value: RequestId) =
writer.append(value.id)

proc read*(rlp: var Rlp, T: type IpAddress): T
{.raises: [RlpError, Defect].} =
let ipBytes = rlp.toBytes()
rlp.skipElem()

if ipBytes.len == 4:
var ip: array[4, byte]
discard copyFrom(ip, ipBytes)
IpAddress(family: IPv4, address_v4: ip)
elif ipBytes.len == 16:
var ip: array[16, byte]
discard copyFrom(ip, ipBytes)
IpAddress(family: IPv6, address_v6: ip)
else:
raise newException(RlpTypeMismatch,
"Amount of bytes for IP address is different from 4 or 16")

proc append*(writer: var RlpWriter, ip: IpAddress) =
case ip.family:
of IpAddressFamily.IPv4:
writer.append(ip.address_v4)
of IpAddressFamily.IPv6: writer.append(ip.address_v6)

proc numFields(T: typedesc): int =
for k, v in fieldPairs(default(T)): inc result

proc encodeMessage*[T: SomeMessage](p: T, reqId: RequestId): seq[byte] =
result = newSeqOfCap[byte](64)
result.add(messageKind(T).ord)

const sz = numFields(T)
var writer = initRlpList(sz + 1)
writer.append(reqId)
for k, v in fieldPairs(p):
writer.append(v)
result.add(writer.finish())

proc decodeMessage*(body: openArray[byte]): DecodeResult[Message] =
## Decodes to the specific `Message` type.
if body.len < 1:
return err("No message data")

var kind: MessageKind
if not checkedEnumAssign(kind, body[0]):
return err("Invalid message type")

var message = Message(kind: kind)
var rlp = rlpFromBytes(body.toOpenArray(1, body.high))
if rlp.enterList:
try:
message.reqId = rlp.read(RequestId)
except RlpError, ValueError:
return err("Invalid request-id")

proc decode[T](rlp: var Rlp, v: var T)
{.nimcall, raises:[RlpError, ValueError, Defect].} =
for k, v in v.fieldPairs:
v = rlp.read(typeof(v))

try:
case kind
of unused: return err("Invalid message type")
of ping: rlp.decode(message.ping)
of pong: rlp.decode(message.pong)
of findNode: rlp.decode(message.findNode)
of nodes: rlp.decode(message.nodes)
of talkReq: rlp.decode(message.talkReq)
of talkResp: rlp.decode(message.talkResp)
of regTopic, ticket, regConfirmation, topicQuery:
# We just pass the empty type of this message without attempting to
# decode, so that the protocol knows what was received.
# But we ignore the message as per specification as "the content and
# semantics of this message are not final".
discard
except RlpError, ValueError:
return err("Invalid message encoding")

ok(message)
else:
err("Invalid message encoding: no rlp list")

Loading