mirror of
https://github.com/status-im/nim-libp2p.git
synced 2025-01-10 04:56:08 +00:00
d02735dc46
Peer Info is now for local peer data only. For other peers info, use the peer store. Previous reference to peer info are replaced with the peerid
280 lines
8.1 KiB
Nim
280 lines
8.1 KiB
Nim
## Nim-LibP2P
|
|
## Copyright (c) 2018 Status Research & Development GmbH
|
|
## Licensed under either of
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
## at your option.
|
|
## This file may not be copied, modified, or distributed except according to
|
|
## those terms.
|
|
|
|
## To enable dump of all incoming and outgoing unencrypted messages you need
|
|
## to compile project with ``-d:libp2p_dump`` compile-time option. When this
|
|
## option enabled ``nim-libp2p`` will create dumps of unencrypted messages for
|
|
## every peer libp2p communicates.
|
|
##
|
|
## Every file is created with name "<PeerID>.pbcap". One file represents
|
|
## all the communication with peer which identified by ``PeerID``.
|
|
##
|
|
## File can have multiple protobuf encoded messages of this format:
|
|
##
|
|
## Protobuf's message fields:
|
|
## 1. SeqID: optional uint64
|
|
## 2. Timestamp: required uint64
|
|
## 3. Type: optional uint64
|
|
## 4. Direction: required uint64
|
|
## 5. LocalAddress: optional bytes
|
|
## 6. RemoteAddress: optional bytes
|
|
## 7. Message: required bytes
|
|
import os, options
|
|
import nimcrypto/utils, stew/endians2
|
|
import protobuf/minprotobuf, stream/connection, protocols/secure/secure,
|
|
multiaddress, peerid, varint, muxers/mplex/coder
|
|
|
|
from times import getTime, toUnix, fromUnix, nanosecond, format, Time,
|
|
NanosecondRange, initTime
|
|
from strutils import toHex, repeat
|
|
export peerid, options, multiaddress
|
|
|
|
type
|
|
FlowDirection* = enum
|
|
Outgoing, Incoming
|
|
|
|
ProtoMessage* = object
|
|
timestamp*: uint64
|
|
direction*: FlowDirection
|
|
message*: seq[byte]
|
|
seqID*: Option[uint64]
|
|
mtype*: Option[uint64]
|
|
local*: Option[MultiAddress]
|
|
remote*: Option[MultiAddress]
|
|
|
|
const
|
|
libp2p_dump_dir* {.strdefine.} = "nim-libp2p"
|
|
## default directory where all the dumps will be stored, if the path
|
|
## relative it will be created in home directory. You can overload this path
|
|
## using ``-d:libp2p_dump_dir=<otherpath>``.
|
|
|
|
proc getTimestamp(): uint64 =
|
|
## This procedure is present because `stdlib.times` missing it.
|
|
let time = getTime()
|
|
uint64(time.toUnix() * 1_000_000_000 + time.nanosecond())
|
|
|
|
proc getTimedate(value: uint64): string =
|
|
## This procedure is workaround for `stdlib.times` to just convert
|
|
## nanoseconds' timestamp to DateTime object.
|
|
let time = initTime(int64(value div 1_000_000_000), value mod 1_000_000_000)
|
|
time.format("yyyy-MM-dd HH:mm:ss'.'fffzzz")
|
|
|
|
proc dumpMessage*(conn: SecureConn, direction: FlowDirection,
|
|
data: openArray[byte]) =
|
|
## Store unencrypted message ``data`` to dump file, all the metadata will be
|
|
## extracted from ``conn`` instance.
|
|
var pb = initProtoBuffer(options = {WithVarintLength})
|
|
pb.write(2, getTimestamp())
|
|
pb.write(4, uint64(direction))
|
|
pb.write(6, conn.observedAddr)
|
|
pb.write(7, data)
|
|
pb.finish()
|
|
|
|
let dirName =
|
|
if isAbsolute(libp2p_dump_dir):
|
|
libp2p_dump_dir
|
|
else:
|
|
getHomeDir() / libp2p_dump_dir
|
|
|
|
let fileName = $(conn.peerId) & ".pbcap"
|
|
|
|
# This is debugging procedure so it should not generate any exceptions,
|
|
# and we going to return at every possible OS error.
|
|
if not(dirExists(dirName)):
|
|
try:
|
|
createDir(dirName)
|
|
except CatchableError:
|
|
return
|
|
|
|
let pathName = dirName / fileName
|
|
var handle: File
|
|
try:
|
|
if open(handle, pathName, fmAppend):
|
|
discard writeBuffer(handle, addr pb.buffer[pb.offset], pb.getLen())
|
|
finally:
|
|
close(handle)
|
|
|
|
proc decodeDumpMessage*(data: openArray[byte]): Option[ProtoMessage] =
|
|
## Decode protobuf's message ProtoMessage from array of bytes ``data``.
|
|
var
|
|
pb = initProtoBuffer(data)
|
|
value: uint64
|
|
ma1, ma2: MultiAddress
|
|
pmsg: ProtoMessage
|
|
|
|
let res2 = pb.getField(2, pmsg.timestamp)
|
|
if res2.isErr() or not(res2.get()):
|
|
return none[ProtoMessage]()
|
|
|
|
let res4 = pb.getField(4, value)
|
|
if res4.isErr() or not(res4.get()):
|
|
return none[ProtoMessage]()
|
|
|
|
# `case` statement could not work here with an error "selector must be of an
|
|
# ordinal type, float or string"
|
|
pmsg.direction =
|
|
if value == uint64(Outgoing):
|
|
Outgoing
|
|
elif value == uint64(Incoming):
|
|
Incoming
|
|
else:
|
|
return none[ProtoMessage]()
|
|
|
|
let res7 = pb.getField(7, pmsg.message)
|
|
if res7.isErr() or not(res7.get()):
|
|
return none[ProtoMessage]()
|
|
|
|
value = 0'u64
|
|
let res1 = pb.getField(1, value)
|
|
if res1.isOk() and res1.get():
|
|
pmsg.seqID = some(value)
|
|
value = 0'u64
|
|
let res3 = pb.getField(3, value)
|
|
if res3.isOk() and res3.get():
|
|
pmsg.mtype = some(value)
|
|
let res5 = pb.getField(5, ma1)
|
|
if res5.isOk() and res5.get():
|
|
pmsg.local = some(ma1)
|
|
let res6 = pb.getField(6, ma2)
|
|
if res6.isOk() and res6.get():
|
|
pmsg.remote = some(ma2)
|
|
|
|
some(pmsg)
|
|
|
|
iterator messages*(data: seq[byte]): Option[ProtoMessage] =
|
|
## Iterate over sequence of bytes and decode all the ``ProtoMessage``
|
|
## messages we found.
|
|
var value: uint64
|
|
var size: int
|
|
var offset = 0
|
|
while offset < len(data):
|
|
value = 0
|
|
size = 0
|
|
let res = PB.getUVarint(data.toOpenArray(offset, len(data) - 1),
|
|
size, value)
|
|
if res.isOk():
|
|
if (value > 0'u64) and (value < uint64(len(data) - offset)):
|
|
offset += size
|
|
yield decodeDumpMessage(data.toOpenArray(offset,
|
|
offset + int(value) - 1))
|
|
# value is previously checked to be less then len(data) which is `int`.
|
|
offset += int(value)
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
|
|
proc dumpHex*(pbytes: openarray[byte], groupBy = 1, ascii = true): string =
|
|
## Get hexadecimal dump of memory for array ``pbytes``.
|
|
var res = ""
|
|
var offset = 0
|
|
var ascii = ""
|
|
|
|
while offset < len(pbytes):
|
|
if (offset mod 16) == 0:
|
|
res = res & toHex(uint64(offset)) & ": "
|
|
|
|
for k in 0 ..< groupBy:
|
|
let ch = pbytes[offset + k]
|
|
ascii.add(if ord(ch) > 31 and ord(ch) < 127: char(ch) else: '.')
|
|
|
|
let item =
|
|
case groupBy:
|
|
of 1:
|
|
toHex(pbytes[offset])
|
|
of 2:
|
|
toHex(uint16.fromBytes(pbytes.toOpenArray(offset, len(pbytes) - 1)))
|
|
of 4:
|
|
toHex(uint32.fromBytes(pbytes.toOpenArray(offset, len(pbytes) - 1)))
|
|
of 8:
|
|
toHex(uint64.fromBytes(pbytes.toOpenArray(offset, len(pbytes) - 1)))
|
|
else:
|
|
""
|
|
res.add(item)
|
|
res.add(" ")
|
|
offset = offset + groupBy
|
|
|
|
if (offset mod 16) == 0:
|
|
res.add(" ")
|
|
res.add(ascii)
|
|
ascii.setLen(0)
|
|
res.add("\p")
|
|
|
|
if (offset mod 16) != 0:
|
|
let spacesCount = ((16 - (offset mod 16)) div groupBy) *
|
|
(groupBy * 2 + 1) + 1
|
|
res = res & repeat(' ', spacesCount)
|
|
res = res & ascii
|
|
|
|
res.add("\p")
|
|
res
|
|
|
|
proc mplexMessage*(data: seq[byte]): string =
|
|
var value = 0'u64
|
|
var size = 0
|
|
let res = PB.getUVarint(data, size, value)
|
|
if res.isOk():
|
|
if size < len(data) and data[size] == 0x00'u8:
|
|
let header = cast[MessageType](value and 0x07'u64)
|
|
let ident = (value shr 3)
|
|
"mplex: [" & $header & ", ident = " & $ident & "] "
|
|
else:
|
|
""
|
|
else:
|
|
""
|
|
|
|
proc toString*(msg: ProtoMessage, dump = true): string =
|
|
## Convert message ``msg`` to its string representation.
|
|
## If ``dump`` is ``true`` (default) full hexadecimal dump with ascii will be
|
|
## used, otherwise just a simple hexadecimal string will be printed.
|
|
var res = getTimedate(msg.timestamp)
|
|
let direction =
|
|
case msg.direction
|
|
of Incoming:
|
|
" << "
|
|
of Outgoing:
|
|
" >> "
|
|
let address =
|
|
block:
|
|
let local =
|
|
if msg.local.isSome():
|
|
"[" & $(msg.local.get()) & "]"
|
|
else:
|
|
"[LOCAL]"
|
|
let remote =
|
|
if msg.remote.isSome():
|
|
"[" & $(msg.remote.get()) & "]"
|
|
else:
|
|
"[REMOTE]"
|
|
local & direction & remote
|
|
let seqid =
|
|
if msg.seqID.isSome():
|
|
"seqID = " & $(msg.seqID.get()) & " "
|
|
else:
|
|
""
|
|
let mtype =
|
|
if msg.mtype.isSome():
|
|
"type = " & $(msg.mtype.get()) & " "
|
|
else:
|
|
""
|
|
res.add(" ")
|
|
res.add(address)
|
|
res.add(" ")
|
|
res.add(mtype)
|
|
res.add(seqid)
|
|
res.add(mplexMessage(msg.message))
|
|
res.add(" ")
|
|
res.add("\p")
|
|
if dump:
|
|
res.add(dumpHex(msg.message))
|
|
else:
|
|
res.add(utils.toHex(msg.message))
|
|
res.add("\p")
|
|
res
|