225 lines
6.7 KiB
Nim
225 lines
6.7 KiB
Nim
|
import std/sequtils
|
||
|
import pkg/chronos
|
||
|
import pkg/chronicles
|
||
|
import pkg/quic
|
||
|
import ../multiaddress
|
||
|
import ../multicodec
|
||
|
import ../stream/connection
|
||
|
import ../wire
|
||
|
import ../muxers/muxer
|
||
|
import ../upgrademngrs/upgrade
|
||
|
import ./transport
|
||
|
|
||
|
export multiaddress
|
||
|
export multicodec
|
||
|
export connection
|
||
|
export transport
|
||
|
|
||
|
logScope:
|
||
|
topics = "libp2p quictransport"
|
||
|
|
||
|
type
|
||
|
P2PConnection = connection.Connection
|
||
|
QuicConnection = quic.Connection
|
||
|
|
||
|
# Stream
|
||
|
type QuicStream* = ref object of P2PConnection
|
||
|
stream: Stream
|
||
|
cached: seq[byte]
|
||
|
|
||
|
proc new(
|
||
|
_: type QuicStream, stream: Stream, oaddr: Opt[MultiAddress], peerId: PeerId
|
||
|
): QuicStream =
|
||
|
let quicstream = QuicStream(stream: stream, observedAddr: oaddr, peerId: peerId)
|
||
|
procCall P2PConnection(quicstream).initStream()
|
||
|
quicstream
|
||
|
|
||
|
template mapExceptions(body: untyped) =
|
||
|
try:
|
||
|
body
|
||
|
except QuicError:
|
||
|
raise newLPStreamEOFError()
|
||
|
except CatchableError:
|
||
|
raise newLPStreamEOFError()
|
||
|
|
||
|
method readOnce*(
|
||
|
stream: QuicStream, pbytes: pointer, nbytes: int
|
||
|
): Future[int] {.async: (raises: [CancelledError, LPStreamError]).} =
|
||
|
try:
|
||
|
if stream.cached.len == 0:
|
||
|
stream.cached = await stream.stream.read()
|
||
|
result = min(nbytes, stream.cached.len)
|
||
|
copyMem(pbytes, addr stream.cached[0], result)
|
||
|
stream.cached = stream.cached[result ..^ 1]
|
||
|
except CatchableError as exc:
|
||
|
raise newLPStreamEOFError()
|
||
|
|
||
|
{.push warning[LockLevel]: off.}
|
||
|
method write*(
|
||
|
stream: QuicStream, bytes: seq[byte]
|
||
|
) {.async: (raises: [CancelledError, LPStreamError]).} =
|
||
|
mapExceptions(await stream.stream.write(bytes))
|
||
|
|
||
|
{.pop.}
|
||
|
|
||
|
method closeImpl*(stream: QuicStream) {.async: (raises: []).} =
|
||
|
try:
|
||
|
await stream.stream.close()
|
||
|
except CatchableError as exc:
|
||
|
discard
|
||
|
await procCall P2PConnection(stream).closeImpl()
|
||
|
|
||
|
# Session
|
||
|
type QuicSession* = ref object of P2PConnection
|
||
|
connection: QuicConnection
|
||
|
|
||
|
method close*(session: QuicSession) {.async, base.} =
|
||
|
await session.connection.close()
|
||
|
await procCall P2PConnection(session).close()
|
||
|
|
||
|
proc getStream*(
|
||
|
session: QuicSession, direction = Direction.In
|
||
|
): Future[QuicStream] {.async.} =
|
||
|
var stream: Stream
|
||
|
case direction
|
||
|
of Direction.In:
|
||
|
stream = await session.connection.incomingStream()
|
||
|
of Direction.Out:
|
||
|
stream = await session.connection.openStream()
|
||
|
await stream.write(@[]) # QUIC streams do not exist until data is sent
|
||
|
return QuicStream.new(stream, session.observedAddr, session.peerId)
|
||
|
|
||
|
method getWrapped*(self: QuicSession): P2PConnection =
|
||
|
nil
|
||
|
|
||
|
# Muxer
|
||
|
type QuicMuxer = ref object of Muxer
|
||
|
quicSession: QuicSession
|
||
|
handleFut: Future[void]
|
||
|
|
||
|
method newStream*(
|
||
|
m: QuicMuxer, name: string = "", lazy: bool = false
|
||
|
): Future[P2PConnection] {.
|
||
|
async: (raises: [CancelledError, LPStreamError, MuxerError])
|
||
|
.} =
|
||
|
try:
|
||
|
return await m.quicSession.getStream(Direction.Out)
|
||
|
except CatchableError as exc:
|
||
|
raise newException(MuxerError, exc.msg, exc)
|
||
|
|
||
|
proc handleStream(m: QuicMuxer, chann: QuicStream) {.async.} =
|
||
|
## call the muxer stream handler for this channel
|
||
|
##
|
||
|
try:
|
||
|
await m.streamHandler(chann)
|
||
|
trace "finished handling stream"
|
||
|
doAssert(chann.closed, "connection not closed by handler!")
|
||
|
except CatchableError as exc:
|
||
|
trace "Exception in mplex stream handler", msg = exc.msg
|
||
|
await chann.close()
|
||
|
|
||
|
method handle*(m: QuicMuxer): Future[void] {.async: (raises: []).} =
|
||
|
try:
|
||
|
while not m.quicSession.atEof:
|
||
|
let incomingStream = await m.quicSession.getStream(Direction.In)
|
||
|
asyncSpawn m.handleStream(incomingStream)
|
||
|
except CatchableError as exc:
|
||
|
trace "Exception in mplex handler", msg = exc.msg
|
||
|
|
||
|
method close*(m: QuicMuxer) {.async: (raises: []).} =
|
||
|
try:
|
||
|
await m.quicSession.close()
|
||
|
m.handleFut.cancel()
|
||
|
except CatchableError as exc:
|
||
|
discard
|
||
|
|
||
|
# Transport
|
||
|
type QuicUpgrade = ref object of Upgrade
|
||
|
|
||
|
type QuicTransport* = ref object of Transport
|
||
|
listener: Listener
|
||
|
connections: seq[P2PConnection]
|
||
|
|
||
|
func new*(_: type QuicTransport, u: Upgrade): QuicTransport =
|
||
|
QuicTransport(upgrader: QuicUpgrade(ms: u.ms))
|
||
|
|
||
|
method handles*(transport: QuicTransport, address: MultiAddress): bool =
|
||
|
if not procCall Transport(transport).handles(address):
|
||
|
return false
|
||
|
QUIC_V1.match(address)
|
||
|
|
||
|
method start*(transport: QuicTransport, addrs: seq[MultiAddress]) {.async.} =
|
||
|
doAssert transport.listener.isNil, "start() already called"
|
||
|
#TODO handle multiple addr
|
||
|
transport.listener = listen(initTAddress(addrs[0]).tryGet)
|
||
|
await procCall Transport(transport).start(addrs)
|
||
|
transport.addrs[0] =
|
||
|
MultiAddress.init(transport.listener.localAddress(), IPPROTO_UDP).tryGet() &
|
||
|
MultiAddress.init("/quic-v1").get()
|
||
|
transport.running = true
|
||
|
|
||
|
method stop*(transport: QuicTransport) {.async.} =
|
||
|
if transport.running:
|
||
|
for c in transport.connections:
|
||
|
await c.close()
|
||
|
await procCall Transport(transport).stop()
|
||
|
await transport.listener.stop()
|
||
|
transport.running = false
|
||
|
transport.listener = nil
|
||
|
|
||
|
proc wrapConnection(
|
||
|
transport: QuicTransport, connection: QuicConnection
|
||
|
): P2PConnection {.raises: [Defect, TransportOsError, LPError].} =
|
||
|
let
|
||
|
remoteAddr = connection.remoteAddress()
|
||
|
observedAddr =
|
||
|
MultiAddress.init(remoteAddr, IPPROTO_UDP).get() &
|
||
|
MultiAddress.init("/quic-v1").get()
|
||
|
conres = QuicSession(connection: connection, observedAddr: Opt.some(observedAddr))
|
||
|
conres.initStream()
|
||
|
|
||
|
transport.connections.add(conres)
|
||
|
proc onClose() {.async.} =
|
||
|
await conres.join()
|
||
|
transport.connections.keepItIf(it != conres)
|
||
|
trace "Cleaned up client"
|
||
|
|
||
|
asyncSpawn onClose()
|
||
|
return conres
|
||
|
|
||
|
method accept*(transport: QuicTransport): Future[P2PConnection] {.async.} =
|
||
|
doAssert not transport.listener.isNil, "call start() before calling accept()"
|
||
|
let connection = await transport.listener.accept()
|
||
|
return transport.wrapConnection(connection)
|
||
|
|
||
|
method dial*(
|
||
|
transport: QuicTransport,
|
||
|
hostname: string,
|
||
|
address: MultiAddress,
|
||
|
peerId: Opt[PeerId] = Opt.none(PeerId),
|
||
|
): Future[P2PConnection] {.async, gcsafe.} =
|
||
|
let connection = await dial(initTAddress(address).tryGet)
|
||
|
return transport.wrapConnection(connection)
|
||
|
|
||
|
method upgrade*(
|
||
|
self: QuicTransport, conn: P2PConnection, peerId: Opt[PeerId]
|
||
|
): Future[Muxer] {.async: (raises: [CancelledError, LPError]).} =
|
||
|
let qs = QuicSession(conn)
|
||
|
if peerId.isSome:
|
||
|
qs.peerId = peerId.get()
|
||
|
|
||
|
let muxer = QuicMuxer(quicSession: qs, connection: conn)
|
||
|
muxer.streamHandler = proc(conn: P2PConnection) {.async: (raises: []).} =
|
||
|
trace "Starting stream handler"
|
||
|
try:
|
||
|
await self.upgrader.ms.handle(conn) # handle incoming connection
|
||
|
except CancelledError as exc:
|
||
|
return
|
||
|
except CatchableError as exc:
|
||
|
trace "exception in stream handler", conn, msg = exc.msg
|
||
|
finally:
|
||
|
await conn.closeWithEOF()
|
||
|
trace "Stream handler done", conn
|
||
|
muxer.handleFut = muxer.handle()
|
||
|
return muxer
|