mirror of
https://github.com/status-im/nim-libp2p.git
synced 2025-03-01 04:50:42 +00:00
215 lines
6.3 KiB
Nim
215 lines
6.3 KiB
Nim
## Nim-LibP2P
|
|
## Copyright (c) 2019 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.
|
|
|
|
import strutils, sequtils
|
|
import chronos, chronicles, stew/byteutils
|
|
import vbuffer,
|
|
protocols/protocol,
|
|
streams/[connection,
|
|
stream,
|
|
pushable,
|
|
asynciters,
|
|
lenprefixed,
|
|
utils]
|
|
|
|
logScope:
|
|
topic = "Multistream"
|
|
|
|
const
|
|
MsgSize* = 64 * 1024
|
|
Codec* = "/multistream/1.0.0"
|
|
|
|
Na* = "na"
|
|
Ls* = "ls"
|
|
|
|
type
|
|
Matcher* = proc (proto: string): bool {.gcsafe.}
|
|
|
|
HandlerHolder* = object
|
|
proto*: string
|
|
protocol*: LPProtocol
|
|
match*: Matcher
|
|
|
|
MultistreamSelect* = object
|
|
handlers*: seq[HandlerHolder]
|
|
codec*: seq[byte]
|
|
na*: seq[byte]
|
|
ls*: seq[byte]
|
|
lp: LenPrefixed
|
|
|
|
MultistreamHandshakeException* = object of CatchableError
|
|
|
|
proc newMultistreamHandshakeException*(): ref Exception {.inline.} =
|
|
result = newException(MultistreamHandshakeException,
|
|
"could not perform multistream handshake")
|
|
|
|
proc init*(M: type[MultistreamSelect]): MultistreamSelect =
|
|
M(codec: toSeq(Codec).mapIt( it.byte ),
|
|
ls: Ls.toBytes(),
|
|
na: Na.toBytes(),
|
|
lp: LenPrefixed.init())
|
|
|
|
proc select*(m: MultistreamSelect,
|
|
conn: Connection,
|
|
protos: seq[string]):
|
|
Future[string] {.async.} =
|
|
trace "initiating handshake", codec = Codec,
|
|
proto = protos
|
|
var pushable = Pushable[seq[byte]].init() # pushable source
|
|
var source = pipe(pushable,
|
|
appendNl(),
|
|
m.lp.encoder,
|
|
conn.toThrough,
|
|
m.lp.decoder,
|
|
stripNl())
|
|
|
|
# handshake first
|
|
await pushable.push(m.codec)
|
|
|
|
# (common optimization) if we've got
|
|
# protos send the first one out immediately
|
|
# without waiting for the handshake response
|
|
if protos.len > 0:
|
|
await pushable.push(protos[0].toBytes())
|
|
|
|
# check for handshake result
|
|
var res = await source()
|
|
if res != m.codec:
|
|
error "handshake failed", codec = result.toHex()
|
|
raise newMultistreamHandshakeException()
|
|
|
|
# write out first protocol without waiting for response
|
|
var i = 0
|
|
while i < protos.len:
|
|
# first read because we've the outstanding requirest above
|
|
trace "reading requested proto"
|
|
res = await source()
|
|
|
|
var protoBytes = protos[i].toBytes()
|
|
if res == protoBytes:
|
|
trace "succesfully selected ", proto = protos[i]
|
|
return protos[i]
|
|
|
|
if i > 0:
|
|
trace "selecting proto", proto = protos[i]
|
|
await pushable.push(protoBytes) # select proto
|
|
i.inc()
|
|
|
|
proc select*(m: MultistreamSelect,
|
|
conn: Connection,
|
|
proto: string): Future[bool] {.async.} =
|
|
if proto.len > 0:
|
|
result = (await m.select(conn, @[proto])) == proto
|
|
else:
|
|
result = (await m.select(conn, @[])) == Codec
|
|
|
|
proc select*(m: MultistreamSelect, conn: Connection): Future[bool] =
|
|
m.select(conn, "")
|
|
|
|
proc list*(m: MultistreamSelect,
|
|
conn: Connection): Future[seq[string]] {.async.} =
|
|
## list remote protos requests on connection
|
|
if not await m.select(conn):
|
|
return
|
|
|
|
var pushable = Pushable[seq[byte]].init()
|
|
var source = pipe(pushable,
|
|
appendNl(),
|
|
m.lp.encoder,
|
|
conn.toThrough,
|
|
m.lp.decoder,
|
|
stripNl())
|
|
|
|
await pushable.push(m.ls) # send ls
|
|
|
|
var list = newSeq[string]()
|
|
for chunk in source:
|
|
var msg = string.fromBytes((await chunk))
|
|
for s in msg.split("\n"):
|
|
if s.len() > 0:
|
|
list.add(s)
|
|
|
|
result = list
|
|
|
|
proc handle*(m: MultistreamSelect, conn: Connection) {.async, gcsafe.} =
|
|
trace "starting multistream handling"
|
|
try:
|
|
var pushable = Pushable[seq[byte]].init()
|
|
var source = pipe(pushable,
|
|
appendNl(),
|
|
m.lp.encoder,
|
|
conn.toThrough,
|
|
m.lp.decoder,
|
|
stripNl())
|
|
|
|
for chunk in source:
|
|
var msg = string.fromBytes((await chunk))
|
|
trace "got request for ", msg
|
|
if msg.len <= 0:
|
|
trace "invalid proto"
|
|
await pushable.push(m.na)
|
|
|
|
if m.handlers.len() == 0:
|
|
trace "sending `na` for protocol ", protocol = msg
|
|
await pushable.push(m.na)
|
|
continue
|
|
|
|
case msg:
|
|
of Ls:
|
|
trace "listing protos"
|
|
var protos: string
|
|
for i in 0..m.handlers.high:
|
|
protos &= m.handlers[i].proto
|
|
if i < m.handlers.high:
|
|
protos &= "\n"
|
|
|
|
await pushable.push(protos.toBytes())
|
|
of Codec:
|
|
trace "handling handshake"
|
|
await pushable.push(m.codec)
|
|
else:
|
|
for h in m.handlers:
|
|
if (not isNil(h.match) and h.match(msg)) or msg == h.proto:
|
|
trace "found handler for", protocol = msg
|
|
await pushable.push(h.proto.toBytes())
|
|
try:
|
|
await h.protocol.handler(conn, msg)
|
|
return
|
|
except CatchableError as exc:
|
|
warn "exception while handling", msg = exc.msg
|
|
return
|
|
warn "no handlers for ", protocol = msg
|
|
await pushable.push(m.na)
|
|
except CatchableError as exc:
|
|
trace "Exception occurred", exc = exc.msg
|
|
finally:
|
|
trace "leaving multistream loop"
|
|
|
|
proc addHandler*(m: var MultistreamSelect,
|
|
codec: string,
|
|
protocol: LPProtocol,
|
|
matcher: Matcher = nil) =
|
|
## register a protocol
|
|
trace "registering protocol", codec = codec
|
|
m.handlers.add(HandlerHolder(proto: codec,
|
|
protocol: protocol,
|
|
match: matcher))
|
|
|
|
proc addHandler*(m: var MultistreamSelect,
|
|
codec: string,
|
|
handler: LPProtoHandler,
|
|
matcher: Matcher = nil) =
|
|
## helper to allow registering pure handlers
|
|
|
|
trace "registering proto handler", codec = codec
|
|
let protocol = LPProtocol(codec: codec, handler: handler)
|
|
m.handlers.add(HandlerHolder(proto: codec,
|
|
protocol: protocol,
|
|
match: matcher))
|