fix: switch, with identify and mplex

This commit is contained in:
Dmitriy Ryajov 2019-09-08 01:43:33 -06:00
parent 701e048ee6
commit e31966b6f8
6 changed files with 61 additions and 94 deletions

View File

@ -103,11 +103,12 @@ proc readLp*(s: Connection): Future[seq[byte]] {.async, gcsafe.} =
await s.readExactly(addr buffer[0], int(size)) await s.readExactly(addr buffer[0], int(size))
except TransportIncompleteError: except TransportIncompleteError:
buffer.setLen(0) buffer.setLen(0)
raise newLPStreamIncompleteError() except AsyncStreamIncompleteError:
buffer.setLen(0)
result = buffer result = buffer
proc writeLp*(s: Connection, msg: string | seq[byte]) {.async, gcsafe.} = proc writeLp*(s: Connection, msg: string | seq[byte]): Future[void] {.gcsafe.} =
## write lenght prefixed ## write lenght prefixed
var buf = initVBuffer() var buf = initVBuffer()
buf.writeSeq(msg) buf.writeSeq(msg)

View File

@ -1,68 +0,0 @@
## 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.
## Small debug module that's enabled through a ``-d:debugout``
## flag. It's inspired by the Nodejs ``debug`` module that
## allows printing colorized output based on patterns. This
## module is powered by the standard ``nre`` module and as such
## it supports the same set of regexp expressions.
##
## To enable debug output, pass the ``debugout`` flag during build
## time with ``-d`` flag. In addition, the debugout flag takes a comma
## separated list of patters that will narow the debug output to
## only those matched by the patterns. By default however, all
## debug statements are outputed to the stderr.
when defined(debugout) and not defined(release):
import random,
tables,
nre,
strutils,
times,
terminal,
sequtils
const debugout {.strdefine.}: string = ".*"
type
Match = object
pattern: Regex
color: string
var matches: OrderedTable[string, Match] = initOrderedTable[string, Match]()
var patrns = @[".*"]
if debugout != "true":
patrns = debugout.split(re"[,\s]").filterIt(it.len > 0)
randomize()
for p in patrns:
matches[p] = Match(pattern: re(p), color: $rand(0..272)) # 256 ansi colors
# matches["*"] = Match(pattern: re(".*"), color: $rand(0..272)) # 256 ansi colors
if isTrueColorSupported():
system.addQuitProc(resetAttributes)
enableTrueColors()
proc doDebug(data: string): void {.gcsafe.} =
for m in matches.values:
if data.match(m.pattern).isSome:
stderr.writeLine("\u001b[38;5;" & m.color & "m " & alignLeft(data, 4) & "\e[0m")
return
template debug*(data: string) =
let module = instantiationInfo()
let line = "$# $#:$# - $#" %
[now().format("yyyy-MM-dd HH:mm:ss:fffffffff"),
module.filename[0 .. ^5],
$module.line,
data]
doDebug(line)
else:
template debug*(data: string) = discard

View File

@ -46,41 +46,49 @@ proc select*(m: MultisteamSelect,
conn: Connection, conn: Connection,
proto: seq[string]): proto: seq[string]):
Future[string] {.async.} = Future[string] {.async.} =
debug &"select: initiating handshake"
## select a remote protocol ## select a remote protocol
await conn.write(m.codec) # write handshake await conn.write(m.codec) # write handshake
if proto.len() > 0: if proto.len() > 0:
debug &"select: selecting proto {proto}"
await conn.writeLp((proto[0] & "\n")) # select proto await conn.writeLp((proto[0] & "\n")) # select proto
result = cast[string](await conn.readLp()) # read ms header result = cast[string](await conn.readLp()) # read ms header
result.removeSuffix("\n") result.removeSuffix("\n")
if result != Codec: if result != Codec:
debug &"select: handshake failed"
return "" return ""
if proto.len() == 0: # no protocols, must be a handshake call if proto.len() == 0: # no protocols, must be a handshake call
return return
result = cast[string](await conn.readLp()) # read the first proto result = cast[string](await conn.readLp()) # read the first proto
debug &"select: reading first requested proto"
result.removeSuffix("\n") result.removeSuffix("\n")
if result == proto[0]: if result == proto[0]:
debug &"select: succesfully selected {proto}"
return return
if not result.len > 0: if not result.len > 0:
debug &"select: selecting one of several protos"
for p in proto[1..<proto.len()]: for p in proto[1..<proto.len()]:
await conn.writeLp((p & "\n")) # select proto await conn.writeLp((p & "\n")) # select proto
result = cast[string](await conn.readLp()) # read the first proto result = cast[string](await conn.readLp()) # read the first proto
result.removeSuffix("\n") result.removeSuffix("\n")
if result == p: if result == p:
debug &"select: selected {result}"
break break
proc select*(m: MultisteamSelect, proc select*(m: MultisteamSelect,
conn: Connection, conn: Connection,
proto: string): Future[bool] {.async.} = proto: string): Future[bool] {.async.} =
result = if proto.len > 0: if proto.len > 0:
(await m.select(conn, @[proto])) == proto result = (await m.select(conn, @[proto])) == proto
else: else:
(await m.select(conn, @[])) == Codec result = (await m.select(conn, @[])) == Codec
proc select*(m: MultisteamSelect, conn: Connection): Future[bool] = m.select(conn, "") proc select*(m: MultisteamSelect, conn: Connection): Future[bool] =
m.select(conn, "")
proc list*(m: MultisteamSelect, proc list*(m: MultisteamSelect,
conn: Connection): Future[seq[string]] {.async.} = conn: Connection): Future[seq[string]] {.async.} =
@ -99,19 +107,25 @@ proc list*(m: MultisteamSelect,
result = list result = list
proc handle*(m: MultisteamSelect, conn: Connection) {.async, gcsafe.} = proc handle*(m: MultisteamSelect, conn: Connection) {.async, gcsafe.} =
debug &"select: starting multistream handling"
while not conn.closed: while not conn.closed:
block main: block main:
var ms = cast[string](await conn.readLp()) var ms = cast[string](await conn.readLp())
ms.removeSuffix("\n") ms.removeSuffix("\n")
debug &"select: got request for {ms}"
if ms.len() <= 0: if ms.len() <= 0:
debug &"select: invalid proto"
await conn.write(m.na) await conn.write(m.na)
if m.handlers.len() == 0: if m.handlers.len() == 0:
debug &"select: {ms} is na"
await conn.write(m.na) await conn.write(m.na)
continue continue
case ms: case ms:
of "ls": of "ls":
debug &"select: listing protos"
var protos = "" var protos = ""
for h in m.handlers: for h in m.handlers:
protos &= (h.proto & "\n") protos &= (h.proto & "\n")
@ -121,12 +135,14 @@ proc handle*(m: MultisteamSelect, conn: Connection) {.async, gcsafe.} =
else: else:
for h in m.handlers: for h in m.handlers:
if (not isNil(h.match) and h.match(ms)) or ms == h.proto: if (not isNil(h.match) and h.match(ms)) or ms == h.proto:
debug &"select: found handler for {ms}"
await conn.writeLp((h.proto & "\n")) await conn.writeLp((h.proto & "\n"))
try: try:
await h.protocol.handler(conn, ms) await h.protocol.handler(conn, ms)
break main break main
except Exception as exc: except Exception as exc:
debug exc.msg # TODO: Logging debug exc.msg # TODO: Logging
debug &"select: no handlers for {ms}"
await conn.write(m.na) await conn.write(m.na)
proc addHandler*[T: LPProtocol](m: MultisteamSelect, proc addHandler*[T: LPProtocol](m: MultisteamSelect,

View File

@ -7,12 +7,16 @@
## This file may not be copied, modified, or distributed except according to ## This file may not be copied, modified, or distributed except according to
## those terms. ## those terms.
import options import options, strformat
import chronos import chronos
import ../protobuf/minprotobuf, ../peerinfo, import ../protobuf/minprotobuf,
protocol as proto, ../connection, ../peerinfo,
../peer, ../crypto/crypto, ../connection,
../multiaddress ../peer,
../crypto/crypto,
../multiaddress,
../protocols/protocol,
../helpers/debug
const IdentifyCodec* = "/ipfs/id/1.0.0" const IdentifyCodec* = "/ipfs/id/1.0.0"
const IdentifyPushCodec* = "/ipfs/id/push/1.0.0" const IdentifyPushCodec* = "/ipfs/id/push/1.0.0"
@ -99,12 +103,15 @@ proc identify*(p: Identify,
Future[IdentifyInfo] {.async.} = Future[IdentifyInfo] {.async.} =
var message = await conn.readLp() var message = await conn.readLp()
if len(message) == 0: if len(message) == 0:
debug "identify: Invalid or empty message received!"
raise newException(IdentityInvalidMsgError, raise newException(IdentityInvalidMsgError,
"Invalid or empty message received!") "Invalid or empty message received!")
result = decodeMsg(message) result = decodeMsg(message)
debug &"identify: Identify for remote peer succeded"
if remotePeerInfo.isSome and if remotePeerInfo.isSome and
remotePeerInfo.get().peerId.publicKey != result.pubKey: remotePeerInfo.get().peerId.publicKey != result.pubKey:
debug "identify: Peer's remote public key doesn't match"
raise newException(IdentityNoMatchError, raise newException(IdentityNoMatchError,
"Peer's remote public key doesn't match") "Peer's remote public key doesn't match")

View File

@ -61,12 +61,13 @@ proc identify(s: Switch, conn: Connection) {.async, gcsafe.} =
peerInfo.peerId = PeerID.init(info.pubKey) # we might not have a peerId at all peerInfo.peerId = PeerID.init(info.pubKey) # we might not have a peerId at all
peerInfo.addrs = info.addrs peerInfo.addrs = info.addrs
peerInfo.protocols = info.protos peerInfo.protocols = info.protos
debug &"identify: identified remote peer {peerInfo.peerId.pretty}"
except IdentityInvalidMsgError as exc: except IdentityInvalidMsgError as exc:
debug exc.msg debug exc.msg
except IdentityNoMatchError as exc: except IdentityNoMatchError as exc:
debug exc.msg debug exc.msg
proc mux(s: Switch, conn: Connection): Future[Connection] {.async, gcsafe.} = proc mux(s: Switch, conn: Connection): Future[void] {.async, gcsafe.} =
## mux incoming connection ## mux incoming connection
let muxers = toSeq(s.muxers.keys) let muxers = toSeq(s.muxers.keys)
let muxerName = await s.ms.select(conn, muxers) let muxerName = await s.ms.select(conn, muxers)
@ -79,9 +80,17 @@ proc mux(s: Switch, conn: Connection): Future[Connection] {.async, gcsafe.} =
# do identify first, so that we have a # do identify first, so that we have a
# PeerInfo in case we didn't before # PeerInfo in case we didn't before
result = await muxer.newStream() let stream = await muxer.newStream()
asyncCheck muxer.handle() let handlerFut = muxer.handle()
await s.identify(result)
# add muxer handler cleanup proc
handlerFut.addCallback(
proc(udata: pointer = nil) {.gcsafe.} =
if handlerFut.finished:
debug &"Muxer handler completed for peer {conn.peerInfo.get().peerId.pretty}"
)
await s.identify(stream)
await stream.close() # close idenity stream
# store it in muxed connections if we have a peer for it # store it in muxed connections if we have a peer for it
# TODO: We should make sure that this are cleaned up properly # TODO: We should make sure that this are cleaned up properly
@ -105,7 +114,7 @@ proc handleConn(s: Switch, conn: Connection): Future[Connection] {.async, gcsafe
s.connections[id] = result s.connections[id] = result
result = await s.secure(conn) # secure the connection result = await s.secure(conn) # secure the connection
result = await s.mux(result) # mux it if possible await s.mux(result) # mux it if possible
proc cleanupConn(s: Switch, conn: Connection) {.async, gcsafe.} = proc cleanupConn(s: Switch, conn: Connection) {.async, gcsafe.} =
let id = if conn.peerInfo.isSome: conn.peerInfo.get().peerId.pretty else: "" let id = if conn.peerInfo.isSome: conn.peerInfo.get().peerId.pretty else: ""
@ -126,12 +135,17 @@ proc dial*(s: Switch,
result = await t.dial(a) result = await t.dial(a)
result.peerInfo = some(peer) result.peerInfo = some(peer)
result = await s.handleConn(result) result = await s.handleConn(result)
# if there is a muxer for the connection
# use it instead to create a muxed stream
if s.muxed.contains(peer.peerId.pretty): if s.muxed.contains(peer.peerId.pretty):
result = await s.muxed[peer.peerId.pretty].newStream() result = await s.muxed[peer.peerId.pretty].newStream()
debug &"dial: attempting to select remote proto {proto}"
if not (await s.ms.select(result, proto)): if not (await s.ms.select(result, proto)):
debug &"dial: Unable to select protocol: {proto}"
raise newException(CatchableError, raise newException(CatchableError,
&"Unable to select protocol: {proto}") &"Unable to select protocol: {proto}")
await s.muxed[peer.peerId.pretty].handle()
proc mount*[T: LPProtocol](s: Switch, proto: T) {.gcsafe.} = proc mount*[T: LPProtocol](s: Switch, proto: T) {.gcsafe.} =
if isNil(proto.handler): if isNil(proto.handler):

View File

@ -12,7 +12,8 @@ import ../libp2p/switch,
../libp2p/protocols/protocol, ../libp2p/protocols/protocol,
../libp2p/muxers/muxer, ../libp2p/muxers/muxer,
../libp2p/muxers/mplex/mplex, ../libp2p/muxers/mplex/mplex,
../libp2p/muxers/mplex/types ../libp2p/muxers/mplex/types,
../libp2p/helpers/debug
const TestCodec = "/test/proto/1.0.0" const TestCodec = "/test/proto/1.0.0"
@ -22,9 +23,9 @@ type
method init(p: TestProto) {.gcsafe.} = method init(p: TestProto) {.gcsafe.} =
proc handle(conn: Connection, proto: string) {.async, gcsafe.} = proc handle(conn: Connection, proto: string) {.async, gcsafe.} =
let msg = cast[string](await conn.readLp()) let msg = cast[string](await conn.readLp())
echo msg
check "Hello!" == msg check "Hello!" == msg
await conn.writeLp("Hello!") await conn.writeLp("Hello!")
await conn.close()
p.codec = TestCodec p.codec = TestCodec
p.handler = handle p.handler = handle
@ -64,13 +65,9 @@ suite "Switch":
(switch2, peerInfo2) = createSwitch(ma2) (switch2, peerInfo2) = createSwitch(ma2)
await switch2.start() await switch2.start()
let conn = await switch2.dial(peerInfo1, TestCodec) let conn = await switch2.dial(peerInfo1, TestCodec)
echo "DIALED???" debug "TEST SWITCH: dial succesful"
echo conn.repr
await conn.writeLp("Hello!") await conn.writeLp("Hello!")
echo "WROTE FROM TEST"
echo conn.repr
let msg = cast[string](await conn.readLp()) let msg = cast[string](await conn.readLp())
echo msg
check "Hello!" == msg check "Hello!" == msg
result = true result = true