mirror of
https://github.com/vacp2p/nim-quic.git
synced 2026-01-08 21:38:05 -05:00
feat: picotls integration (#55)
This commit is contained in:
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -23,18 +23,26 @@ jobs:
|
||||
cpu: i386
|
||||
shell: bash
|
||||
- os: macos
|
||||
runner: macos-latest
|
||||
runner: macos-13
|
||||
cpu: amd64
|
||||
shell: bash
|
||||
- os: macos
|
||||
runner: macos-14
|
||||
cpu: arm64
|
||||
shell: bash
|
||||
- os: windows
|
||||
runner: windows-latest
|
||||
cpu: amd64
|
||||
shell: msys2 {0}
|
||||
nim:
|
||||
- branch: version-1-6
|
||||
- branch: version-2-0
|
||||
- ref: version-1-6
|
||||
- ref: version-2-0
|
||||
|
||||
name: '${{ matrix.platform.os }}-${{ matrix.platform.cpu }} (Nim ${{ matrix.nim.branch }})'
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
|
||||
name: '${{ matrix.platform.os }}-${{ matrix.platform.cpu }} (Nim ${{ matrix.nim.ref }})'
|
||||
runs-on: ${{ matrix.platform.runner }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -46,7 +54,12 @@ jobs:
|
||||
os: ${{ matrix.platform.os }}
|
||||
cpu: ${{ matrix.platform.cpu }}
|
||||
shell: ${{ matrix.platform.shell }}
|
||||
nim_branch: ${{ matrix.nim.branch }}
|
||||
nim_ref: ${{ matrix.nim.ref }}
|
||||
|
||||
- name: Install deps (windows)
|
||||
if : ${{ matrix.platform.os == 'windows'}}
|
||||
run: |
|
||||
pacman -S --noconfirm base-devel gcc mingw-w64-x86_64-openssl
|
||||
|
||||
- name: Install dependencies
|
||||
run: nimble install -y
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
*
|
||||
!*/
|
||||
!*.*
|
||||
|
||||
@@ -8,6 +8,6 @@ requires "nim >= 1.6.0"
|
||||
requires "stew#head"
|
||||
requires "chronos >= 4.0.3 & < 5.0.0"
|
||||
requires "nimcrypto >= 0.6.0 & < 0.7.0"
|
||||
requires "ngtcp2#6834f4756b6af58356ac9c4fef3d71db3c3ae5fe"
|
||||
requires "ngtcp2 >= 0.35.0"
|
||||
requires "unittest2"
|
||||
requires "chronicles >= 0.10.2"
|
||||
|
||||
75
quic/api.nim
75
quic/api.nim
@@ -1,10 +1,13 @@
|
||||
import pkg/chronos
|
||||
import chronos
|
||||
import results
|
||||
import ./listener
|
||||
import ./connection
|
||||
import ./udp/datagram
|
||||
import ./errors
|
||||
import ./transport/tlsbackend
|
||||
|
||||
export Listener
|
||||
export accept
|
||||
export Connection
|
||||
export Stream
|
||||
export openStream
|
||||
@@ -18,20 +21,76 @@ export drop
|
||||
export close
|
||||
export waitClosed
|
||||
export errors
|
||||
export destroy
|
||||
export CertificateVerifier
|
||||
export certificateVerifierCB
|
||||
export CustomCertificateVerifier
|
||||
export InsecureCertificateVerifier
|
||||
export init
|
||||
|
||||
proc listen*(address: TransportAddress): Listener =
|
||||
newListener(address)
|
||||
type TLSConfig* = object
|
||||
certificate: seq[byte]
|
||||
key: seq[byte]
|
||||
certificateVerifier: Opt[CertificateVerifier]
|
||||
|
||||
proc accept*(listener: Listener): Future[Connection] {.async.} =
|
||||
result = await listener.waitForIncoming()
|
||||
type Quic = ref object of RootObj
|
||||
tlsConfig: TLSConfig
|
||||
|
||||
proc dial*(address: TransportAddress): Future[Connection] {.async.} =
|
||||
type QuicClient* = object of Quic
|
||||
|
||||
type QuicServer* = object of Quic
|
||||
|
||||
proc init*(
|
||||
t: typedesc[TLSConfig],
|
||||
certificate: seq[byte] = @[],
|
||||
key: seq[byte] = @[],
|
||||
certificateVerifier: Opt[CertificateVerifier] = Opt.none(CertificateVerifier),
|
||||
): TLSConfig {.gcsafe, raises: [QuicConfigError].} =
|
||||
# In a config, certificate and keys are optional, but if using them, both must
|
||||
# be specified at the same time
|
||||
if certificate.len != 0 or key.len != 0:
|
||||
if certificate.len == 0:
|
||||
raise newException(QuicConfigError, "certificate is required in TLSConfig")
|
||||
|
||||
if key.len == 0:
|
||||
raise newException(QuicConfigError, "key is required in TLSConfig")
|
||||
|
||||
return TLSConfig(
|
||||
certificate: certificate, key: key, certificateVerifier: certificateVerifier
|
||||
)
|
||||
|
||||
proc init*(
|
||||
t: typedesc[QuicServer], tlsConfig: TLSConfig
|
||||
): QuicServer {.raises: [QuicConfigError].} =
|
||||
if tlsConfig.certificate.len == 0:
|
||||
raise newException(QuicConfigError, "tlsConfig does not contain a certificate")
|
||||
|
||||
return QuicServer(tlsConfig: tlsConfig)
|
||||
|
||||
proc init*(t: typedesc[QuicClient], tlsConfig: TLSConfig): QuicClient {.raises: [].} =
|
||||
return QuicClient(tlsConfig: tlsConfig)
|
||||
|
||||
proc listen*(
|
||||
self: QuicServer, address: TransportAddress
|
||||
): Listener {.raises: [QuicError, TransportOsError].} =
|
||||
let tlsBackend = newServerTLSBackend(
|
||||
self.tlsConfig.certificate, self.tlsConfig.key, self.tlsConfig.certificateVerifier
|
||||
)
|
||||
|
||||
return newListener(tlsBackend, address)
|
||||
|
||||
proc dial*(
|
||||
self: QuicClient, address: TransportAddress
|
||||
): Future[Connection] {.async: (raises: [QuicError, TransportOsError]).} =
|
||||
let tlsBackend = newClientTLSBackend(
|
||||
self.tlsConfig.certificate, self.tlsConfig.key, self.tlsConfig.certificateVerifier
|
||||
)
|
||||
var connection: Connection
|
||||
proc onReceive(udp: DatagramTransport, remote: TransportAddress) {.async.} =
|
||||
let datagram = Datagram(data: udp.getMessage())
|
||||
connection.receive(datagram)
|
||||
|
||||
let udp = newDatagramTransport(onReceive)
|
||||
connection = newOutgoingConnection(udp, address)
|
||||
connection = newOutgoingConnection(tlsBackend, udp, address)
|
||||
connection.startHandshake()
|
||||
result = connection
|
||||
return connection
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import pkg/chronos
|
||||
import pkg/stew/results
|
||||
import results
|
||||
import ./udp/datagram
|
||||
import ./errors
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import ./transport/connectionid
|
||||
import ./transport/stream
|
||||
import ./transport/quicconnection
|
||||
import ./transport/quicclientserver
|
||||
import ./transport/tlsbackend
|
||||
import ./helpers/asyncloop
|
||||
|
||||
export Stream, close, read, write
|
||||
@@ -22,7 +23,9 @@ type
|
||||
closed: AsyncEvent
|
||||
|
||||
IncomingConnection = ref object of Connection
|
||||
|
||||
OutgoingConnection = ref object of Connection
|
||||
tlsBackend: Opt[TLSBackend]
|
||||
|
||||
proc ids*(connection: Connection): seq[ConnectionId] =
|
||||
connection.quic.ids
|
||||
@@ -43,9 +46,19 @@ proc drop*(connection: Connection) {.async.} =
|
||||
|
||||
proc close*(connection: Connection) {.async.} =
|
||||
await connection.quic.close()
|
||||
if connection is OutgoingConnection:
|
||||
let outConn = OutgoingConnection(connection)
|
||||
if outConn.tlsBackend.isSome:
|
||||
outConn.tlsBackend.get().destroy()
|
||||
outConn.tlsBackend = Opt.none(TLSBackend)
|
||||
|
||||
proc waitClosed*(connection: Connection) {.async.} =
|
||||
await connection.closed.wait()
|
||||
if connection is OutgoingConnection:
|
||||
let outConn = OutgoingConnection(connection)
|
||||
if outConn.tlsBackend.isSome:
|
||||
outConn.tlsBackend.get().destroy()
|
||||
outConn.tlsBackend = Opt.none(TLSBackend)
|
||||
|
||||
proc startSending(connection: Connection, remote: TransportAddress) =
|
||||
trace "Starting sending loop"
|
||||
@@ -53,9 +66,9 @@ proc startSending(connection: Connection, remote: TransportAddress) =
|
||||
try:
|
||||
trace "Getting datagram"
|
||||
let datagram = await connection.quic.outgoing.get()
|
||||
trace "Sending datagraom"
|
||||
trace "Sending datagram"
|
||||
await connection.udp.sendTo(remote, datagram.data)
|
||||
trace "Sent datagraom"
|
||||
trace "Sent datagram"
|
||||
except TransportError as e:
|
||||
trace "Failed to send datagram", errorMsg = e.msg
|
||||
trace "Failing connection loop future with error"
|
||||
@@ -94,10 +107,10 @@ proc disconnect(connection: Connection) {.async.} =
|
||||
trace "Fired closed event"
|
||||
|
||||
proc newIncomingConnection*(
|
||||
udp: DatagramTransport, remote: TransportAddress
|
||||
tlsBackend: TLSBackend, udp: DatagramTransport, remote: TransportAddress
|
||||
): Connection =
|
||||
let datagram = Datagram(data: udp.getMessage())
|
||||
let quic = newQuicServerConnection(udp.localAddress, remote, datagram)
|
||||
let quic = newQuicServerConnection(tlsBackend, udp.localAddress, remote, datagram)
|
||||
let closed = newAsyncEvent()
|
||||
let connection = IncomingConnection(udp: udp, quic: quic, closed: closed)
|
||||
proc onDisconnect() {.async.} =
|
||||
@@ -111,11 +124,13 @@ proc newIncomingConnection*(
|
||||
connection
|
||||
|
||||
proc newOutgoingConnection*(
|
||||
udp: DatagramTransport, remote: TransportAddress
|
||||
tlsBackend: TLSBackend, udp: DatagramTransport, remote: TransportAddress
|
||||
): Connection =
|
||||
let quic = newQuicClientConnection(udp.localAddress, remote)
|
||||
let quic = newQuicClientConnection(tlsBackend, udp.localAddress, remote)
|
||||
let closed = newAsyncEvent()
|
||||
let connection = OutgoingConnection(udp: udp, quic: quic, closed: closed)
|
||||
let connection = OutgoingConnection(
|
||||
udp: udp, quic: quic, closed: closed, tlsBackend: Opt.some(tlsBackend)
|
||||
)
|
||||
proc onDisconnect() {.async.} =
|
||||
trace "Calling onDisconnect for newOutgoingConnection"
|
||||
await connection.disconnect()
|
||||
@@ -126,7 +141,7 @@ proc newOutgoingConnection*(
|
||||
connection.startSending(remote)
|
||||
connection
|
||||
|
||||
proc startHandshake*(connection: Connection) =
|
||||
proc startHandshake*(connection: Connection) {.gcsafe.} =
|
||||
connection.quic.send()
|
||||
|
||||
proc receive*(connection: Connection, datagram: Datagram) =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
type
|
||||
QuicError* = object of IOError
|
||||
QuicDefect* = object of Defect
|
||||
QuicConfigError* = object of CatchableError
|
||||
|
||||
template errorAsDefect*(body): untyped =
|
||||
try:
|
||||
|
||||
@@ -3,8 +3,10 @@ import ./basics
|
||||
import ./connection
|
||||
import ./transport/connectionid
|
||||
import ./transport/parsedatagram
|
||||
import ./transport/tlsbackend
|
||||
|
||||
type Listener* = ref object
|
||||
tlsBackend: TLSBackend
|
||||
udp: DatagramTransport
|
||||
incoming: AsyncQueue[Connection]
|
||||
connections: Table[ConnectionId, Connection]
|
||||
@@ -42,23 +44,30 @@ proc getOrCreateConnection*(
|
||||
var connection: Connection
|
||||
let destination = parseDatagram(udp.getMessage()).destination
|
||||
if not listener.hasConnection(destination):
|
||||
connection = newIncomingConnection(udp, remote)
|
||||
connection = newIncomingConnection(listener.tlsBackend, udp, remote)
|
||||
listener.addConnection(connection, destination)
|
||||
else:
|
||||
connection = listener.getConnection(destination)
|
||||
connection
|
||||
|
||||
proc newListener*(address: TransportAddress): Listener =
|
||||
proc newListener*(tlsBackend: TLSBackend, address: TransportAddress): Listener =
|
||||
let listener = Listener(incoming: newAsyncQueue[Connection]())
|
||||
proc onReceive(udp: DatagramTransport, remote: TransportAddress) {.async.} =
|
||||
let connection = listener.getOrCreateConnection(udp, remote)
|
||||
connection.receive(Datagram(data: udp.getMessage()))
|
||||
|
||||
listener.tlsBackend = tlsBackend
|
||||
listener.udp = newDatagramTransport(onReceive, local = address)
|
||||
listener
|
||||
|
||||
proc waitForIncoming*(listener: Listener): Future[Connection] {.async.} =
|
||||
result = await listener.incoming.get()
|
||||
await listener.incoming.get()
|
||||
|
||||
proc accept*(listener: Listener): Future[Connection] {.async.} =
|
||||
result = await listener.waitForIncoming()
|
||||
|
||||
proc stop*(listener: Listener) {.async.} =
|
||||
await listener.udp.closeWait()
|
||||
|
||||
proc destroy*(listener: Listener) =
|
||||
listener.tlsBackend.destroy()
|
||||
|
||||
@@ -15,8 +15,6 @@ type
|
||||
proc newClosedConnection*(): ClosedConnection =
|
||||
ClosedConnection()
|
||||
|
||||
{.push locks: "unknown".}
|
||||
|
||||
method ids(state: ClosedConnection): seq[ConnectionId] =
|
||||
@[]
|
||||
|
||||
@@ -38,5 +36,3 @@ method drop(state: ClosedConnection) {.async.} =
|
||||
trace "Dropping ClosedConnection state"
|
||||
discard
|
||||
trace "Dropped ClosedConnection state"
|
||||
|
||||
{.pop.}
|
||||
|
||||
@@ -21,13 +21,9 @@ proc sendFinalDatagram(state: ClosingConnection) =
|
||||
except AsyncQueueFullError:
|
||||
raise newException(QuicError, "Outgoing queue is full")
|
||||
|
||||
{.push locks: "unknown".}
|
||||
|
||||
method enter(state: ClosingConnection, connection: QuicConnection) =
|
||||
procCall enter(DrainingConnection(state), connection)
|
||||
state.sendFinalDatagram()
|
||||
|
||||
method receive(state: ClosingConnection, datagram: Datagram) =
|
||||
state.sendFinalDatagram()
|
||||
|
||||
{.pop.}
|
||||
|
||||
@@ -24,8 +24,6 @@ proc callDisconnect(connection: QuicConnection) {.async.} =
|
||||
await disconnect()
|
||||
trace "Called disconnect proc on QuicConnection"
|
||||
|
||||
{.push locks: "unknown".}
|
||||
|
||||
method ids*(state: DisconnectingConnection): seq[ConnectionId] =
|
||||
state.ids
|
||||
|
||||
@@ -68,5 +66,3 @@ method drop(state: DisconnectingConnection) {.async.} =
|
||||
return
|
||||
connection.switch(newClosedConnection())
|
||||
trace "dropped DisconnectingConnection state"
|
||||
|
||||
{.pop.}
|
||||
|
||||
@@ -4,10 +4,12 @@ import ../../../basics
|
||||
import ../../quicconnection
|
||||
import ../../connectionid
|
||||
import ../../stream
|
||||
import ../../tlsbackend
|
||||
import ../native/connection
|
||||
import ../native/streams
|
||||
import ../native/client
|
||||
import ../native/server
|
||||
import ../native/errors
|
||||
import ./closingstate
|
||||
import ./drainingstate
|
||||
import ./disconnectingstate
|
||||
@@ -24,42 +26,59 @@ type OpenConnection* = ref object of ConnectionState
|
||||
proc newOpenConnection*(ngtcp2Connection: Ngtcp2Connection): OpenConnection =
|
||||
OpenConnection(ngtcp2Connection: ngtcp2Connection, streams: OpenStreams.new)
|
||||
|
||||
proc openClientConnection*(local, remote: TransportAddress): OpenConnection =
|
||||
newOpenConnection(newNgtcp2Client(local, remote))
|
||||
proc openClientConnection*(
|
||||
tlsBackend: TLSBackend, local, remote: TransportAddress
|
||||
): OpenConnection =
|
||||
let ngtcp2Conn = newNgtcp2Client(tlsBackend.picoTLS, local, remote)
|
||||
newOpenConnection(ngtcp2Conn)
|
||||
|
||||
proc openServerConnection*(
|
||||
local, remote: TransportAddress, datagram: Datagram
|
||||
tlsBackend: TLSBackend, local, remote: TransportAddress, datagram: Datagram
|
||||
): OpenConnection =
|
||||
newOpenConnection(newNgtcp2Server(local, remote, datagram.data))
|
||||
newOpenConnection(newNgtcp2Server(tlsBackend.picoTLS, local, remote, datagram.data))
|
||||
|
||||
{.push locks: "unknown".}
|
||||
|
||||
method close(state: OpenConnection) {.async.}
|
||||
|
||||
method enter(state: OpenConnection, connection: QuicConnection) =
|
||||
trace "Entering OpenConnection state"
|
||||
procCall enter(ConnectionState(state), connection)
|
||||
state.quicConnection = Opt.some(connection)
|
||||
# Workaround weird bug
|
||||
proc onNewId(id: ConnectionId) =
|
||||
var onNewId = proc(id: ConnectionId) =
|
||||
if isNil(connection.onNewId):
|
||||
return
|
||||
connection.onNewId(id)
|
||||
|
||||
state.ngtcp2Connection.onNewId = Opt.some(onNewId)
|
||||
|
||||
proc onRemoveId(id: ConnectionId) =
|
||||
var onRemoveId = proc(id: ConnectionId) =
|
||||
if isNil(connection.onRemoveId):
|
||||
return
|
||||
connection.onRemoveId(id)
|
||||
|
||||
state.ngtcp2Connection.onNewId = Opt.some(onNewId)
|
||||
state.ngtcp2Connection.onRemoveId = Opt.some(onRemoveId)
|
||||
|
||||
state.ngtcp2Connection.onSend = proc(datagram: Datagram) =
|
||||
errorAsDefect:
|
||||
connection.outgoing.putNoWait(datagram)
|
||||
|
||||
state.ngtcp2Connection.onIncomingStream = proc(stream: Stream) =
|
||||
state.streams.add(stream)
|
||||
connection.incoming.putNoWait(stream)
|
||||
state.ngtcp2Connection.onHandshakeDone = proc() =
|
||||
connection.handshake.fire()
|
||||
|
||||
state.ngtcp2Connection.onTimeout = proc() {.gcsafe, raises: [].} =
|
||||
try:
|
||||
waitFor connection.close()
|
||||
except QuicError:
|
||||
# TODO: handle
|
||||
discard
|
||||
except CatchableError:
|
||||
# TODO: handle
|
||||
discard
|
||||
|
||||
trace "Entered OpenConnection state"
|
||||
|
||||
method leave(state: OpenConnection) =
|
||||
@@ -77,15 +96,24 @@ method send(state: OpenConnection) =
|
||||
state.ngtcp2Connection.send()
|
||||
|
||||
method receive(state: OpenConnection, datagram: Datagram) =
|
||||
state.ngtcp2Connection.receive(datagram)
|
||||
let quicConnection = state.quicConnection.valueOr:
|
||||
return
|
||||
if state.ngtcp2Connection.isDraining:
|
||||
let duration = state.ngtcp2Connection.closingDuration()
|
||||
let ids = state.ids
|
||||
let draining = newDrainingConnection(ids, duration)
|
||||
quicConnection.switch(draining)
|
||||
asyncSpawn draining.close()
|
||||
var isDraining = false
|
||||
try:
|
||||
state.ngtcp2Connection.receive(datagram)
|
||||
except Ngtcp2Error as e:
|
||||
trace "ngtcp2 error on receive", code = $e.msg
|
||||
isDraining = state.ngtcp2Connection.isDraining
|
||||
# TODO:
|
||||
# if not isDraining:
|
||||
# raise newException(QuicError, "could not receive - code:" & $e.msg)
|
||||
finally:
|
||||
let quicConnection = state.quicConnection.valueOr:
|
||||
return
|
||||
if isDraining:
|
||||
let duration = state.ngtcp2Connection.closingDuration()
|
||||
let ids = state.ids
|
||||
let draining = newDrainingConnection(ids, duration)
|
||||
quicConnection.switch(draining)
|
||||
asyncSpawn draining.close()
|
||||
|
||||
method openStream(
|
||||
state: OpenConnection, unidirectional: bool
|
||||
|
||||
@@ -4,6 +4,8 @@ import ./native/client
|
||||
import ./native/handshake
|
||||
import ./native/streams
|
||||
import ./native/parsedatagram
|
||||
import ./native/picotls
|
||||
import ./native/certificateverifier
|
||||
|
||||
export parseDatagram
|
||||
export Ngtcp2Connection
|
||||
@@ -15,3 +17,10 @@ export handshake
|
||||
export ids
|
||||
export openStream
|
||||
export destroy
|
||||
|
||||
export PicoTLSContext
|
||||
export PicoTLSConnection
|
||||
export init
|
||||
export destroy
|
||||
export newConnection
|
||||
export certificateverifier
|
||||
7
quic/transport/ngtcp2/native/certificateverifier.nim
Normal file
7
quic/transport/ngtcp2/native/certificateverifier.nim
Normal file
@@ -0,0 +1,7 @@
|
||||
import ./certificateverifier/certificateverifier
|
||||
import ./certificateverifier/custom
|
||||
import ./certificateverifier/insecure
|
||||
|
||||
export certificateverifier
|
||||
export custom
|
||||
export insecure
|
||||
@@ -0,0 +1,11 @@
|
||||
import ngtcp2
|
||||
|
||||
type CertificateVerifier* = ref object of RootObj
|
||||
|
||||
method destroy*(t: CertificateVerifier) {.base, gcsafe.} =
|
||||
doAssert false, "override this method"
|
||||
|
||||
method getPtlsVerifyCertificateT*(
|
||||
t: CertificateVerifier
|
||||
): ptr ptls_verify_certificate_t {.base, gcsafe.} =
|
||||
doAssert false, "override this method"
|
||||
70
quic/transport/ngtcp2/native/certificateverifier/custom.nim
Normal file
70
quic/transport/ngtcp2/native/certificateverifier/custom.nim
Normal file
@@ -0,0 +1,70 @@
|
||||
import ngtcp2
|
||||
import sequtils
|
||||
import ./certificateverifier
|
||||
import ../pointers
|
||||
import ../../../../helpers/openarray
|
||||
|
||||
type
|
||||
certificateVerifierCB* =
|
||||
proc(derCertificates: seq[seq[byte]]): bool {.gcsafe, noSideEffect.}
|
||||
|
||||
customPTLSVerifyCertificateT = object of ptls_verify_certificate_t
|
||||
customCertVerifier: certificateVerifierCB
|
||||
|
||||
CustomCertificateVerifier* = ref object of CertificateVerifier
|
||||
verifier: ptr customPTLSVerifyCertificateT
|
||||
|
||||
proc validateCertificate(
|
||||
self: ptr ptls_verify_certificate_t,
|
||||
tls: ptr ptls_t,
|
||||
server_name: cstring,
|
||||
verify_sign: proc(
|
||||
verify_ctx: pointer, algo: uint16, data: ptls_iovec_t, sign: ptls_iovec_t
|
||||
): cint {.cdecl.},
|
||||
verify_data: ptr pointer,
|
||||
certs: ptr ptls_iovec_t,
|
||||
num_certs: csize_t,
|
||||
): cint {.cdecl.} =
|
||||
let certVerifier = cast[ptr customPTLSVerifyCertificateT](self)
|
||||
if certVerifier.customCertVerifier.isNil:
|
||||
doAssert false, "custom cert verifier was not setup"
|
||||
|
||||
var derCertificates = newSeq[seq[byte]](num_certs)
|
||||
for i in 0 ..< int(num_certs):
|
||||
let cert = certs + i
|
||||
derCertificates[i] = toSeq(toOpenArray(cert.base, cert.len))
|
||||
|
||||
if certVerifier.customCertVerifier(derCertificates):
|
||||
return 0
|
||||
else:
|
||||
return PTLS_ALERT_BAD_CERTIFICATE
|
||||
|
||||
proc init*(
|
||||
t: typedesc[CustomCertificateVerifier], certVerifierCB: certificateVerifierCB
|
||||
): CustomCertificateVerifier {.gcsafe.} =
|
||||
let response = CustomCertificateVerifier()
|
||||
response.verifier = create(customPTLSVerifyCertificateT)
|
||||
var algos = cast[ptr UncheckedArray[uint16]](alloc(uint16.sizeof * 5))
|
||||
algos[0] = PTLS_SIGNATURE_RSA_PSS_RSAE_SHA256
|
||||
algos[1] = PTLS_SIGNATURE_ECDSA_SECP256R1_SHA256
|
||||
algos[2] = PTLS_SIGNATURE_RSA_PKCS1_SHA256
|
||||
algos[3] = PTLS_SIGNATURE_RSA_PKCS1_SHA1
|
||||
algos[4] = high(uint16)
|
||||
response.verifier.cb = validateCertificate
|
||||
response.verifier.algos = cast[ptr uint16](algos)
|
||||
response.verifier.customCertVerifier = certVerifierCB
|
||||
return response
|
||||
|
||||
method destroy*(t: CustomCertificateVerifier) {.gcsafe.} =
|
||||
if t.verifier.isNil:
|
||||
return
|
||||
|
||||
let algosPtr = cast[pointer](t.verifier.algos)
|
||||
dealloc(algosPtr)
|
||||
dealloc(t.verifier)
|
||||
t.verifier = nil
|
||||
|
||||
method getPtlsVerifyCertificateT*(
|
||||
t: CustomCertificateVerifier
|
||||
): ptr ptls_verify_certificate_t =
|
||||
return t.verifier
|
||||
@@ -0,0 +1,17 @@
|
||||
import ngtcp2
|
||||
import ./certificateverifier
|
||||
|
||||
type InsecureCertificateVerifier* = ref object of CertificateVerifier
|
||||
|
||||
proc init*(t: typedesc[InsecureCertificateVerifier]): InsecureCertificateVerifier {.gcsafe.} =
|
||||
return InsecureCertificateVerifier()
|
||||
|
||||
method destroy*(t: InsecureCertificateVerifier) {.gcsafe.} =
|
||||
discard
|
||||
|
||||
method getPtlsVerifyCertificateT*(
|
||||
t: InsecureCertificateVerifier
|
||||
): ptr ptls_verify_certificate_t =
|
||||
# picotls will check against null to determine whether a certificate verifier
|
||||
# was setup or not
|
||||
return nil
|
||||
@@ -1,75 +1,31 @@
|
||||
import pkg/ngtcp2
|
||||
import pkg/nimcrypto
|
||||
import ngtcp2
|
||||
import ../../../errors
|
||||
import ../../version
|
||||
import ../../../basics
|
||||
import ../../../helpers/openarray
|
||||
import ../../connectionid
|
||||
import ./ids
|
||||
import ./encryption
|
||||
import ./keys
|
||||
import ./settings
|
||||
import ./cryptodata
|
||||
import ./connection
|
||||
import ./path
|
||||
import ./picotls
|
||||
import ./rand
|
||||
import ./streams
|
||||
import ./timestamp
|
||||
import ./handshake
|
||||
|
||||
proc onClientInitial(connection: ptr ngtcp2_conn, user_data: pointer): cint {.cdecl.} =
|
||||
connection.install0RttKey()
|
||||
connection.submitCryptoData(NGTCP2_ENCRYPTION_LEVEL_INITIAL)
|
||||
|
||||
proc onReceiveCryptoData(
|
||||
connection: ptr ngtcp2_conn,
|
||||
level: ngtcp2_encryption_level,
|
||||
offset: uint64,
|
||||
data: ptr uint8,
|
||||
datalen: uint,
|
||||
userData: pointer,
|
||||
): cint {.cdecl.} =
|
||||
if level == NGTCP2_ENCRYPTION_LEVEL_INITIAL:
|
||||
connection.installHandshakeKeys()
|
||||
if level == NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
|
||||
connection.handleCryptoData(toOpenArray(data, datalen))
|
||||
connection.install1RttKeys()
|
||||
connection.submitCryptoData(NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE)
|
||||
ngtcp2_conn_tls_handshake_completed(connection)
|
||||
|
||||
proc onReceiveRetry(
|
||||
connection: ptr ngtcp2_conn, hd: ptr ngtcp2_pkt_hd, userData: pointer
|
||||
): cint {.cdecl.} =
|
||||
return 0
|
||||
|
||||
proc onRand(dest: ptr uint8, destLen: uint, rand_ctx: ptr ngtcp2_rand_ctx) {.cdecl.} =
|
||||
doAssert destLen.int == randomBytes(dest, destLen.int)
|
||||
|
||||
proc onDeleteCryptoAeadCtx(
|
||||
conn: ptr ngtcp2_conn, aead_ctx: ptr ngtcp2_crypto_aead_ctx, userData: pointer
|
||||
) {.cdecl.} =
|
||||
discard
|
||||
|
||||
proc onDeleteCryptoCipherCtx(
|
||||
conn: ptr ngtcp2_conn, cipher_ctx: ptr ngtcp2_crypto_cipher_ctx, userData: pointer
|
||||
) {.cdecl.} =
|
||||
discard
|
||||
|
||||
proc onGetPathChallengeData(
|
||||
conn: ptr ngtcp2_conn, data: ptr uint8, userData: pointer
|
||||
): cint {.cdecl.} =
|
||||
let bytesWritten = randomBytes(data, NGTCP2_PATH_CHALLENGE_DATALEN)
|
||||
if bytesWritten != NGTCP2_PATH_CHALLENGE_DATALEN:
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE
|
||||
return 0
|
||||
|
||||
proc newNgtcp2Client*(local, remote: TransportAddress): Ngtcp2Connection =
|
||||
proc newNgtcp2Client*(
|
||||
tlsContext: PicoTLSContext, local, remote: TransportAddress
|
||||
): Ngtcp2Connection =
|
||||
var callbacks: ngtcp2_callbacks
|
||||
callbacks.client_initial = onClientInitial
|
||||
callbacks.recv_crypto_data = onReceiveCryptoData
|
||||
callbacks.recv_retry = onReceiveRetry
|
||||
callbacks.client_initial = ngtcp2_crypto_client_initial_cb
|
||||
callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb
|
||||
callbacks.recv_retry = ngtcp2_crypto_recv_retry_cb
|
||||
callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb
|
||||
callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb
|
||||
callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb
|
||||
callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb
|
||||
callbacks.rand = onRand
|
||||
callbacks.delete_crypto_aead_ctx = onDeleteCryptoAeadCtx
|
||||
callbacks.delete_crypto_cipher_ctx = onDeleteCryptoCipherCtx
|
||||
callbacks.get_path_challenge_data = onGetPathChallengeData
|
||||
|
||||
installConnectionIdCallback(callbacks)
|
||||
installEncryptionCallbacks(callbacks)
|
||||
@@ -83,24 +39,61 @@ proc newNgtcp2Client*(local, remote: TransportAddress): Ngtcp2Connection =
|
||||
let destination = randomConnectionId().toCid
|
||||
let path = newPath(local, remote)
|
||||
|
||||
result = newConnection(path)
|
||||
let nConn = newConnection(path)
|
||||
|
||||
var conn: ptr ngtcp2_conn
|
||||
var ret = ngtcp2_conn_client_new_versioned(
|
||||
addr conn,
|
||||
unsafeAddr destination,
|
||||
unsafeAddr source,
|
||||
path.toPathPtr,
|
||||
CurrentQuicVersion,
|
||||
NGTCP2_CALLBACKS_V1,
|
||||
addr callbacks,
|
||||
NGTCP2_SETTINGS_V2,
|
||||
unsafeAddr settings,
|
||||
NGTCP2_TRANSPORT_PARAMS_V1,
|
||||
unsafeAddr transportParams,
|
||||
nil,
|
||||
addr nConn[],
|
||||
)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not create new client versioned conn: " & $ret)
|
||||
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_client_new_versioned(
|
||||
addr conn,
|
||||
unsafeAddr destination,
|
||||
unsafeAddr source,
|
||||
path.toPathPtr,
|
||||
CurrentQuicVersion,
|
||||
NGTCP2_CALLBACKS_V1,
|
||||
addr callbacks,
|
||||
NGTCP2_SETTINGS_V2,
|
||||
unsafeAddr settings,
|
||||
NGTCP2_TRANSPORT_PARAMS_V1,
|
||||
unsafeAddr transportParams,
|
||||
nil,
|
||||
addr result[],
|
||||
)
|
||||
let cptls: ptr ngtcp2_crypto_picotls_ctx = create(ngtcp2_crypto_picotls_ctx)
|
||||
|
||||
result.conn = Opt.some(conn)
|
||||
ngtcp2_crypto_picotls_ctx_init(cptls)
|
||||
|
||||
var tls = tlsContext.newConnection(false)
|
||||
cptls.ptls = tls.conn
|
||||
|
||||
var addExtensions = cast[ptr UncheckedArray[ptls_raw_extension_t]](alloc(
|
||||
ptls_raw_extension_t.sizeof * 2
|
||||
))
|
||||
addExtensions[0] = ptls_raw_extension_t(type_field: high(uint16))
|
||||
addExtensions[1] = ptls_raw_extension_t(type_field: high(uint16))
|
||||
cptls.handshake_properties = ptls_handshake_properties_t(
|
||||
additional_extensions: cast[ptr ptls_raw_extension_t](addExtensions)
|
||||
)
|
||||
|
||||
ngtcp2_conn_set_tls_native_handle(conn, cptls)
|
||||
|
||||
var connref = create(ngtcp2_crypto_conn_ref)
|
||||
connref.user_data = conn
|
||||
connref.get_conn = proc(
|
||||
connRef: ptr ngtcp2_crypto_conn_ref
|
||||
): ptr ngtcp2_conn {.cdecl.} =
|
||||
cast[ptr ngtcp2_conn](connRef.user_data)
|
||||
|
||||
var dataPtr = ptls_get_data_ptr(tls.conn)
|
||||
dataPtr[] = connref
|
||||
|
||||
ret = ngtcp2_crypto_picotls_configure_client_session(cptls, conn)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not configure client session: " & $ret)
|
||||
|
||||
nConn.conn = Opt.some(conn)
|
||||
nConn.tlsConn = tls
|
||||
nConn.cptls = cptls
|
||||
nConn.connref = connref
|
||||
nConn
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import std/sequtils
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import chronicles
|
||||
import ../../../basics
|
||||
import ../../../udp/congestion
|
||||
import ../../../helpers/openarray
|
||||
@@ -7,18 +8,27 @@ import ../../stream
|
||||
import ../../timeout
|
||||
import ../../connectionid
|
||||
import ./path
|
||||
import ./picotls
|
||||
import ./errors as ngtcp2errors
|
||||
import ./timestamp
|
||||
import ./pointers
|
||||
|
||||
logScope:
|
||||
topics = "ngtcp2 conn"
|
||||
|
||||
type
|
||||
Ngtcp2Connection* = ref object
|
||||
conn*: Opt[ptr ngtcp2_conn]
|
||||
tlsConn*: PicoTLSConnection
|
||||
cptls*: ptr ngtcp2_crypto_picotls_ctx
|
||||
connref*: ptr ngtcp2_crypto_conn_ref
|
||||
|
||||
path*: Path
|
||||
buffer*: array[4096, byte]
|
||||
flowing*: AsyncEvent
|
||||
timeout*: Timeout
|
||||
onSend*: proc(datagram: Datagram) {.gcsafe, raises: [].}
|
||||
onTimeout*: proc() {.raises: [].}
|
||||
onIncomingStream*: proc(stream: Stream)
|
||||
onHandshakeDone*: proc()
|
||||
onNewId*: Opt[proc(id: ConnectionId)]
|
||||
@@ -31,6 +41,14 @@ proc destroy*(connection: Ngtcp2Connection) =
|
||||
return
|
||||
connection.timeout.stop()
|
||||
ngtcp2_conn_del(conn)
|
||||
ngtcp2_crypto_picotls_deconfigure_session(connection.cptls)
|
||||
connection.tlsConn.destroy()
|
||||
dealloc(connection.cptls.handshake_properties.additional_extensions)
|
||||
dealloc(connection.connref)
|
||||
dealloc(connection.cptls)
|
||||
connection.cptls = nil
|
||||
connection.connref = nil
|
||||
connection.tlsConn = nil
|
||||
connection.conn = Opt.none(ptr ngtcp2_conn)
|
||||
connection.onSend = nil
|
||||
connection.onIncomingStream = nil
|
||||
@@ -40,6 +58,8 @@ proc destroy*(connection: Ngtcp2Connection) =
|
||||
|
||||
proc handleTimeout(connection: Ngtcp2Connection) {.gcsafe, raises: [].}
|
||||
|
||||
proc executeOnTimeout(connection: Ngtcp2Connection) {.async.}
|
||||
|
||||
proc newConnection*(path: Path): Ngtcp2Connection =
|
||||
let connection = Ngtcp2Connection()
|
||||
connection.path = path
|
||||
@@ -49,6 +69,9 @@ proc newConnection*(path: Path): Ngtcp2Connection =
|
||||
connection.handleTimeout()
|
||||
)
|
||||
connection.flowing.fire()
|
||||
|
||||
asyncSpawn connection.executeOnTimeout()
|
||||
|
||||
connection
|
||||
|
||||
proc ids*(connection: Ngtcp2Connection): seq[ConnectionId] =
|
||||
@@ -63,7 +86,7 @@ proc ids*(connection: Ngtcp2Connection): seq[ConnectionId] =
|
||||
proc updateTimeout*(connection: Ngtcp2Connection) =
|
||||
let conn = connection.conn.valueOr:
|
||||
raise newException(Ngtcp2ConnectionClosed, "connection no longer exists")
|
||||
|
||||
trace "updateTimeout"
|
||||
let expiry = ngtcp2_conn_get_expiry(conn)
|
||||
if expiry != uint64.high:
|
||||
connection.timeout.set(Moment.init(expiry.int64, 1.nanoseconds))
|
||||
@@ -88,8 +111,8 @@ proc trySend(
|
||||
addr packetInfo,
|
||||
addr connection.buffer[0],
|
||||
connection.buffer.len.uint,
|
||||
written,
|
||||
0,
|
||||
cast[ptr ngtcp2_ssize](written),
|
||||
NGTCP2_WRITE_STREAM_FLAG_NONE,
|
||||
streamId,
|
||||
messagePtr,
|
||||
messageLen,
|
||||
@@ -116,6 +139,7 @@ proc send(
|
||||
messagePtr: ptr byte,
|
||||
messageLen: uint,
|
||||
): Future[int] {.async.} =
|
||||
|
||||
let written = addr result
|
||||
var datagram = trySend(connection, streamId, messagePtr, messageLen, written)
|
||||
while datagram.data.len == 0:
|
||||
@@ -154,10 +178,7 @@ proc tryReceive(connection: Ngtcp2Connection, datagram: openArray[byte], ecn: EC
|
||||
proc receive*(
|
||||
connection: Ngtcp2Connection, datagram: openArray[byte], ecn = ecnNonCapable
|
||||
) =
|
||||
try:
|
||||
connection.tryReceive(datagram, ecn)
|
||||
except Ngtcp2Error:
|
||||
return
|
||||
connection.tryReceive(datagram, ecn)
|
||||
connection.send()
|
||||
connection.flowing.fire()
|
||||
|
||||
@@ -169,13 +190,20 @@ proc handleTimeout(connection: Ngtcp2Connection) =
|
||||
return
|
||||
|
||||
errorAsDefect:
|
||||
checkResult ngtcp2_conn_handle_expiry(conn, now())
|
||||
let ret = ngtcp2_conn_handle_expiry(conn, now())
|
||||
trace "handleExpiry", ret
|
||||
checkResult ret
|
||||
connection.send()
|
||||
|
||||
proc close*(connection: Ngtcp2Connection): Datagram =
|
||||
let conn = connection.conn.valueOr:
|
||||
raise newException(Ngtcp2ConnectionClosed, "connection no longer exists")
|
||||
|
||||
if (
|
||||
ngtcp2_conn_in_closing_period(conn) == 1 or ngtcp2_conn_in_draining_period(conn) == 1
|
||||
):
|
||||
return
|
||||
|
||||
var ccerr: ngtcp2_ccerr
|
||||
ngtcp2_ccerr_default(addr ccerr)
|
||||
|
||||
@@ -195,6 +223,14 @@ proc close*(connection: Ngtcp2Connection): Datagram =
|
||||
let ecn = ECN(packetInfo.ecn)
|
||||
Datagram(data: data, ecn: ecn)
|
||||
|
||||
# TODO: should stop all event loops
|
||||
|
||||
proc executeOnTimeout(connection: Ngtcp2Connection) {.async.} =
|
||||
trace "Waiting expiration"
|
||||
await connection.timeout.expired()
|
||||
trace "Timeout expired"
|
||||
#TODO should we call connection.onTimeout()
|
||||
|
||||
proc closingDuration*(connection: Ngtcp2Connection): Duration =
|
||||
let conn = connection.conn.valueOr:
|
||||
raise newException(Ngtcp2ConnectionClosed, "connection no longer exists")
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import pkg/ngtcp2
|
||||
import ./params
|
||||
|
||||
proc submitCryptoData*(connection: ptr ngtcp2_conn, level: ngtcp2_encryption_level) =
|
||||
var cryptoData = connection.encodeTransportParameters()
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_submit_crypto_data(
|
||||
connection, level, addr cryptoData[0], cryptoData.len.uint
|
||||
)
|
||||
|
||||
proc handleCryptoData*(connection: ptr ngtcp2_conn, data: openArray[byte]) =
|
||||
let parameters = decodeTransportParameters(data)
|
||||
connection.setRemoteTransportParameters(parameters)
|
||||
@@ -1,61 +1,7 @@
|
||||
import pkg/ngtcp2
|
||||
import ./pointers
|
||||
|
||||
const aeadlen* = 16
|
||||
|
||||
proc dummyEncrypt(
|
||||
dest: ptr uint8,
|
||||
aead: ptr ngtcp2_crypto_aead,
|
||||
aead_ctx: ptr ngtcp2_crypto_aead_ctx,
|
||||
plaintext: ptr uint8,
|
||||
plaintextlen: uint,
|
||||
nonce: ptr uint8,
|
||||
noncelen: uint,
|
||||
ad: ptr uint8,
|
||||
adlen: uint,
|
||||
): cint {.cdecl.} =
|
||||
moveMem(dest, plaintext, plaintextlen)
|
||||
zeroMem(dest + plaintextlen.int, aeadlen)
|
||||
|
||||
proc dummyDecrypt(
|
||||
dest: ptr uint8,
|
||||
aead: ptr ngtcp2_crypto_aead,
|
||||
aead_ctx: ptr ngtcp2_crypto_aead_ctx,
|
||||
ciphertext: ptr uint8,
|
||||
ciphertextlen: uint,
|
||||
nonce: ptr uint8,
|
||||
noncelen: uint,
|
||||
ad: ptr uint8,
|
||||
adlen: uint,
|
||||
): cint {.cdecl.} =
|
||||
moveMem(dest, ciphertext, ciphertextlen - aeadlen)
|
||||
|
||||
proc dummyHpMask(
|
||||
dest: ptr uint8,
|
||||
hp: ptr ngtcp2_crypto_cipher,
|
||||
hpContext: ptr ngtcp2_crypto_cipher_ctx,
|
||||
sample: ptr uint8,
|
||||
): cint {.cdecl.} =
|
||||
var NGTCP2_FAKE_HP_MASK = "\x00\x00\x00\x00\x00"
|
||||
copyMem(dest, addr NGTCP2_FAKE_HP_MASK[0], NGTCP2_FAKE_HP_MASK.len)
|
||||
|
||||
proc dummyUpdateKey(
|
||||
conn: ptr ngtcp2_conn,
|
||||
rx_secret: ptr uint8,
|
||||
tx_secret: ptr uint8,
|
||||
rx_aead_ctx: ptr ngtcp2_crypto_aead_ctx,
|
||||
rx_iv: ptr uint8,
|
||||
tx_aead_ctx: ptr ngtcp2_crypto_aead_ctx,
|
||||
tx_iv: ptr uint8,
|
||||
current_rx_secret: ptr uint8,
|
||||
current_tx_secret: ptr uint8,
|
||||
secretlen: uint,
|
||||
user_data: pointer,
|
||||
): cint {.cdecl.} =
|
||||
discard
|
||||
import ngtcp2
|
||||
|
||||
proc installEncryptionCallbacks*(callbacks: var ngtcp2_callbacks) =
|
||||
callbacks.encrypt = dummyEncrypt
|
||||
callbacks.decrypt = dummyDecrypt
|
||||
callbacks.hp_mask = dummyHpMask
|
||||
callbacks.update_key = dummyUpdateKey
|
||||
callbacks.encrypt = ngtcp2_crypto_encrypt_cb
|
||||
callbacks.decrypt = ngtcp2_crypto_decrypt_cb
|
||||
callbacks.hp_mask = ngtcp2_crypto_hp_mask_cb
|
||||
callbacks.update_key = ngtcp2_crypto_update_key_cb
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ../../../errors
|
||||
|
||||
type
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ./connection
|
||||
|
||||
proc onHandshakeDone(connection: ptr ngtcp2_conn, userData: pointer): cint {.cdecl.} =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ../../../basics
|
||||
import ../../../helpers/openarray
|
||||
import ../../connectionid
|
||||
@@ -18,13 +18,19 @@ proc getNewConnectionId(
|
||||
conn: ptr ngtcp2_conn,
|
||||
id: ptr ngtcp2_cid,
|
||||
token: ptr uint8,
|
||||
cidlen: uint,
|
||||
cidlen: csize_t,
|
||||
userData: pointer,
|
||||
): cint {.cdecl.} =
|
||||
let newId = randomConnectionId(cidlen.int)
|
||||
id[] = newId.toCid
|
||||
zeroMem(token, NGTCP2_STATELESS_RESET_TOKENLEN)
|
||||
|
||||
# TODO: should ngtcp2_crypto_generate_stateless_reset_token so
|
||||
# we can signal the other peer that the connection is no longer valid?
|
||||
# ngtcp2_crypto_generate_stateless_reset_token(
|
||||
# token, some_static_secret_data, config.static_secret.size(), cid) !=
|
||||
# 0)
|
||||
|
||||
let
|
||||
connection = cast[Ngtcp2Connection](userData)
|
||||
onNewId = connection.onNewId.valueOr:
|
||||
@@ -44,3 +50,4 @@ proc removeConnectionId(
|
||||
proc installConnectionIdCallback*(callbacks: var ngtcp2_callbacks) =
|
||||
callbacks.get_new_connection_id = getNewConnectionId
|
||||
callbacks.remove_connection_id = removeConnectionId
|
||||
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import pkg/ngtcp2
|
||||
import ../../../helpers/openarray
|
||||
import ./encryption
|
||||
|
||||
type
|
||||
Secret = seq[byte]
|
||||
AuthenticatedEncryptionWithAssociatedData = object
|
||||
context: ngtcp2_crypto_aead_ctx
|
||||
|
||||
HeaderProtection = object
|
||||
context: ngtcp2_crypto_cipher_ctx
|
||||
|
||||
Key = object
|
||||
aead: AuthenticatedEncryptionWithAssociatedData
|
||||
hp: HeaderProtection
|
||||
iv: seq[byte]
|
||||
|
||||
CryptoContext = ngtcp2_crypto_ctx
|
||||
|
||||
proc dummyCryptoContext(): CryptoContext =
|
||||
var ctx = CryptoContext()
|
||||
ctx.max_encryption = 1000
|
||||
ctx.aead.max_overhead = aeadlen
|
||||
return ctx
|
||||
|
||||
proc dummyKey(): Key =
|
||||
Key(iv: cast[seq[byte]]("dummykey"))
|
||||
|
||||
proc dummySecret(): Secret =
|
||||
cast[seq[byte]]("dummysecret")
|
||||
|
||||
proc install0RttKey*(connection: ptr ngtcp2_conn) =
|
||||
let context = dummyCryptoContext()
|
||||
connection.ngtcp2_conn_set_initial_crypto_ctx(unsafeAddr context)
|
||||
let key = dummyKey()
|
||||
doAssert 0 ==
|
||||
connection.ngtcp2_conn_install_initial_key(
|
||||
key.aead.context.unsafeAddr, key.iv.toUnsafePtr, key.hp.context.unsafeAddr,
|
||||
key.aead.context.unsafeAddr, key.iv.toUnsafePtr, key.hp.context.unsafeAddr,
|
||||
key.iv.len.uint,
|
||||
)
|
||||
|
||||
proc installHandshakeKeys*(connection: ptr ngtcp2_conn) =
|
||||
let context = dummyCryptoContext()
|
||||
connection.ngtcp2_conn_set_crypto_ctx(unsafeAddr context)
|
||||
let rx, tx = dummyKey()
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_install_rx_handshake_key(
|
||||
connection, rx.aead.context.unsafeAddr, rx.iv.toUnsafePtr, rx.iv.len.uint,
|
||||
rx.hp.context.unsafeAddr,
|
||||
)
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_install_tx_handshake_key(
|
||||
connection, tx.aead.context.unsafeAddr, tx.iv.toUnsafePtr, tx.iv.len.uint,
|
||||
tx.hp.context.unsafeAddr,
|
||||
)
|
||||
|
||||
proc install1RttKeys*(connection: ptr ngtcp2_conn) =
|
||||
let secret = dummySecret()
|
||||
let rx, tx = dummyKey()
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_install_rx_key(
|
||||
connection, secret.toUnsafePtr, secret.len.uint, rx.aead.context.unsafeAddr,
|
||||
rx.iv.toUnsafePtr, rx.iv.len.uint, rx.hp.context.unsafeAddr,
|
||||
)
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_install_tx_key(
|
||||
connection, secret.toUnsafePtr, secret.len.uint, tx.aead.context.unsafeAddr,
|
||||
tx.iv.toUnsafePtr, tx.iv.len.uint, tx.hp.context.unsafeAddr,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ./errors
|
||||
|
||||
type TransportParameters* = ngtcp2_transport_params
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import std/nativesockets
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ../../../basics
|
||||
|
||||
type Path* = ref object
|
||||
storage: ngtcp2_path_storage
|
||||
path: ngtcp2_path
|
||||
localAddress: Sockaddr_storage
|
||||
localAddrLen: SockLen
|
||||
remoteAddress: Sockaddr_storage
|
||||
remoteAddrLen: SockLen
|
||||
|
||||
proc toPathPtr*(path: Path): ptr ngtcp2_path =
|
||||
addr path.storage.path
|
||||
addr path.path
|
||||
|
||||
proc newPath*(local, remote: TransportAddress): Path =
|
||||
var localAddress, remoteAddress: Sockaddr_storage
|
||||
var localLength, remoteLength: SockLen
|
||||
local.toSAddr(localAddress, localLength)
|
||||
remote.toSAddr(remoteAddress, remoteLength)
|
||||
var path = Path()
|
||||
ngtcp2_path_storage_init(
|
||||
addr path.storage,
|
||||
cast[ptr SockAddr](addr localAddress),
|
||||
localLength,
|
||||
cast[ptr SockAddr](addr remoteAddress),
|
||||
remoteLength,
|
||||
nil,
|
||||
var p = Path()
|
||||
local.toSAddr(p.localAddress, p.localAddrLen)
|
||||
remote.toSAddr(p.remoteAddress, p.remoteAddrLen)
|
||||
p.path = ngtcp2_path(
|
||||
local: ngtcp2_addr(
|
||||
addr_field: cast[ptr ngtcp2_sockaddr](p.localAddress.addr),
|
||||
addr_len: p.localAddrLen,
|
||||
),
|
||||
remote: ngtcp2_addr(
|
||||
addr_field: cast[ptr ngtcp2_sockaddr](p.remoteAddress.addr),
|
||||
addr_len: p.remoteAddrLen,
|
||||
),
|
||||
user_data: nil
|
||||
)
|
||||
path
|
||||
p
|
||||
|
||||
110
quic/transport/ngtcp2/native/picotls.nim
Normal file
110
quic/transport/ngtcp2/native/picotls.nim
Normal file
@@ -0,0 +1,110 @@
|
||||
import ngtcp2
|
||||
import results
|
||||
import ../../../errors
|
||||
import tables
|
||||
import ./certificateverifier
|
||||
|
||||
type
|
||||
PicoTLSContext* = ref object
|
||||
context*: ptr ptls_context_t
|
||||
signCert: ptr ptls_openssl_sign_certificate_t
|
||||
certVerifier: Opt[CertificateVerifier]
|
||||
|
||||
PicoTLSConnection* = ref object
|
||||
conn*: ptr ptls_t
|
||||
|
||||
proc loadCertificate(ctx: ptr ptls_context_t, certificate: seq[byte]) =
|
||||
var buf = create(ptls_cred_buffer_t)
|
||||
defer:
|
||||
dealloc(buf)
|
||||
buf.off = 0
|
||||
buf.owns_base = 0
|
||||
buf.len = uint(len(certificate))
|
||||
buf.base = newString(buf.len).cstring
|
||||
copyMem(buf.base[0].unsafeAddr, certificate[0].unsafeAddr, buf.len)
|
||||
|
||||
let ret = ptls_load_certificates_from_memory(ctx, buf)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not load certificate: " & $ret)
|
||||
|
||||
proc loadPrivateKey(signCert: ptr ptls_openssl_sign_certificate_t, key: seq[byte]) =
|
||||
let ret =
|
||||
ptls_openssl_init_sign_certificate_with_mem_key(signCert, key[0].unsafeAddr, key.len.cint)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not load private key: " & $ret)
|
||||
|
||||
proc init*(
|
||||
t: typedesc[PicoTLSContext],
|
||||
certificate: seq[byte],
|
||||
key: seq[byte],
|
||||
certVerifier: Opt[CertificateVerifier],
|
||||
requiresClientAuthentication: bool,
|
||||
): PicoTLSContext =
|
||||
var ctx = create(ptls_context_t)
|
||||
ctx.random_bytes = ptls_openssl_random_bytes
|
||||
ctx.get_time = addr ptls_get_time
|
||||
ctx.key_exchanges =
|
||||
cast[ptr ptr ptls_key_exchange_algorithm_t](addr ptls_openssl_key_exchanges)
|
||||
ctx.cipher_suites = cast[ptr ptr ptls_cipher_suite_t](addr ptls_openssl_cipher_suites)
|
||||
|
||||
if certVerifier.isSome:
|
||||
if requiresClientAuthentication:
|
||||
ctx.require_client_authentication = 1
|
||||
try:
|
||||
ctx.verify_certificate = certVerifier.get().getPtlsVerifyCertificateT()
|
||||
except:
|
||||
doAssert false, "checked with if"
|
||||
else:
|
||||
ctx.verify_certificate = nil
|
||||
|
||||
var signCert: ptr ptls_openssl_sign_certificate_t = nil
|
||||
if len(key) != 0 and len(certificate) != 0:
|
||||
signCert = create(ptls_openssl_sign_certificate_t)
|
||||
loadPrivateKey(signCert, key)
|
||||
loadCertificate(ctx, certificate)
|
||||
ctx.sign_certificate = addr signCert.super
|
||||
|
||||
return PicoTLSContext(context: ctx, signCert: signCert, certVerifier: certVerifier)
|
||||
|
||||
proc cfree(p: pointer) {.importc: "free", header: "<stdlib.h>".}
|
||||
|
||||
proc destroy*(p: PicoTLSContext) =
|
||||
if p.context == nil:
|
||||
return
|
||||
|
||||
if not p.signCert.isNil:
|
||||
ptls_openssl_dispose_sign_certificate(p.signCert)
|
||||
let arr = cast[ptr UncheckedArray[ptls_iovec_t]](p.context.certificates.list)
|
||||
for i in 0 ..< p.context.certificates.count:#
|
||||
cfree(arr[i].base)
|
||||
cfree(p.context.certificates.list)
|
||||
dealloc(p.signCert)
|
||||
p.context.certificates.list = nil
|
||||
p.context.certificates.count = 0
|
||||
p.signCert = nil
|
||||
|
||||
if p.certVerifier.isSome:
|
||||
try:
|
||||
p.certVerifier.get().destroy()
|
||||
except:
|
||||
doAssert false, "checked with if"
|
||||
p.certVerifier = Opt.none(CertificateVerifier)
|
||||
|
||||
dealloc(p.context)
|
||||
p.context = nil
|
||||
|
||||
proc newConnection*(p: PicoTLSContext, isServer: bool): PicoTLSConnection =
|
||||
return PicoTLSConnection(
|
||||
conn:
|
||||
if isServer:
|
||||
ptls_server_new(p.context)
|
||||
else:
|
||||
ptls_client_new(p.context)
|
||||
)
|
||||
|
||||
proc destroy*(p: PicoTLSConnection) =
|
||||
if p.conn == nil:
|
||||
return
|
||||
|
||||
ptls_free(p.conn)
|
||||
p.conn = nil
|
||||
@@ -1,2 +1,2 @@
|
||||
proc `+`*[T](p: ptr T, a: int): ptr T =
|
||||
cast[ptr T](cast[ByteAddress](p) + ByteAddress(a))
|
||||
cast[ptr T](cast[uint](p) + uint(a))
|
||||
|
||||
8
quic/transport/ngtcp2/native/rand.nim
Normal file
8
quic/transport/ngtcp2/native/rand.nim
Normal file
@@ -0,0 +1,8 @@
|
||||
import ngtcp2
|
||||
import nimcrypto
|
||||
|
||||
proc onRand*(
|
||||
dest: ptr uint8, destLen: csize_t, rand_ctx: ptr ngtcp2_rand_ctx
|
||||
) {.cdecl.} =
|
||||
# TODO: external source of randomness?
|
||||
doAssert destLen.int == randomBytes(dest, destLen.int)
|
||||
@@ -1,76 +1,33 @@
|
||||
import pkg/ngtcp2
|
||||
import pkg/nimcrypto
|
||||
|
||||
import ../../../basics
|
||||
import ../../../helpers/openarray
|
||||
import ../../../errors
|
||||
import ../../packets
|
||||
import ../../version
|
||||
import ./encryption
|
||||
import ./ids
|
||||
import ./keys
|
||||
import ./settings
|
||||
import ./cryptodata
|
||||
import ./connection
|
||||
import ./path
|
||||
import ./picotls
|
||||
import ./rand
|
||||
import ./streams
|
||||
import ./timestamp
|
||||
import ./handshake
|
||||
import ./parsedatagram
|
||||
|
||||
proc onReceiveClientInitial(
|
||||
connection: ptr ngtcp2_conn, dcid: ptr ngtcp2_cid, userData: pointer
|
||||
): cint {.cdecl.} =
|
||||
connection.install0RttKey()
|
||||
|
||||
proc onReceiveCryptoData(
|
||||
connection: ptr ngtcp2_conn,
|
||||
level: ngtcp2_encryption_level,
|
||||
offset: uint64,
|
||||
data: ptr uint8,
|
||||
datalen: uint,
|
||||
userData: pointer,
|
||||
): cint {.cdecl.} =
|
||||
if level == NGTCP2_ENCRYPTION_LEVEL_INITIAL:
|
||||
connection.submitCryptoData(NGTCP2_ENCRYPTION_LEVEL_INITIAL)
|
||||
connection.installHandshakeKeys()
|
||||
connection.handleCryptoData(toOpenArray(data, datalen))
|
||||
connection.submitCryptoData(NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE)
|
||||
connection.install1RttKeys()
|
||||
if level == NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
|
||||
connection.submitCryptoData(NGTCP2_ENCRYPTION_LEVEL_1RTT)
|
||||
ngtcp2_conn_tls_handshake_completed(connection)
|
||||
|
||||
proc onRand(dest: ptr uint8, destLen: uint, rand_ctx: ptr ngtcp2_rand_ctx) {.cdecl.} =
|
||||
doAssert destLen.int == randomBytes(dest, destLen.int)
|
||||
|
||||
proc onDeleteCryptoAeadCtx(
|
||||
conn: ptr ngtcp2_conn, aead_ctx: ptr ngtcp2_crypto_aead_ctx, userData: pointer
|
||||
) {.cdecl.} =
|
||||
discard
|
||||
|
||||
proc onDeleteCryptoCipherCtx(
|
||||
conn: ptr ngtcp2_conn, cipher_ctx: ptr ngtcp2_crypto_cipher_ctx, userData: pointer
|
||||
) {.cdecl.} =
|
||||
discard
|
||||
|
||||
proc onGetPathChallengeData(
|
||||
conn: ptr ngtcp2_conn, data: ptr uint8, userData: pointer
|
||||
): cint {.cdecl.} =
|
||||
let bytesWritten = randomBytes(data, NGTCP2_PATH_CHALLENGE_DATALEN)
|
||||
if bytesWritten != NGTCP2_PATH_CHALLENGE_DATALEN:
|
||||
return NGTCP2_ERR_CALLBACK_FAILURE
|
||||
return 0
|
||||
|
||||
proc newNgtcp2Server*(
|
||||
local, remote: TransportAddress, source, destination: ngtcp2_cid
|
||||
tlsContext: PicoTLSContext,
|
||||
local, remote: TransportAddress,
|
||||
source, destination: ngtcp2_cid,
|
||||
): Ngtcp2Connection =
|
||||
var callbacks: ngtcp2_callbacks
|
||||
callbacks.recv_client_initial = onReceiveClientInitial
|
||||
callbacks.recv_crypto_data = onReceiveCryptoData
|
||||
callbacks.recv_client_initial = ngtcp2_crypto_recv_client_initial_cb
|
||||
callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb
|
||||
callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb
|
||||
callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb
|
||||
callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb
|
||||
callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb
|
||||
callbacks.rand = onRand
|
||||
callbacks.delete_crypto_aead_ctx = onDeleteCryptoAeadCtx
|
||||
callbacks.delete_crypto_cipher_ctx = onDeleteCryptoCipherCtx
|
||||
callbacks.get_path_challenge_data = onGetPathChallengeData
|
||||
|
||||
installConnectionIdCallback(callbacks)
|
||||
installEncryptionCallbacks(callbacks)
|
||||
@@ -86,34 +43,71 @@ proc newNgtcp2Server*(
|
||||
let id = randomConnectionId().toCid
|
||||
let path = newPath(local, remote)
|
||||
|
||||
result = newConnection(path)
|
||||
let nConn = newConnection(path)
|
||||
|
||||
var conn: ptr ngtcp2_conn
|
||||
var ret = ngtcp2_conn_server_new_versioned(
|
||||
addr conn,
|
||||
unsafeAddr source,
|
||||
unsafeAddr id,
|
||||
path.toPathPtr,
|
||||
CurrentQuicVersion,
|
||||
NGTCP2_CALLBACKS_V1,
|
||||
addr callbacks,
|
||||
NGTCP2_SETTINGS_V2,
|
||||
addr settings,
|
||||
NGTCP2_TRANSPORT_PARAMS_V1,
|
||||
addr transportParams,
|
||||
nil,
|
||||
addr nConn[],
|
||||
)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not create new server versioned conn: " & $ret)
|
||||
|
||||
doAssert 0 ==
|
||||
ngtcp2_conn_server_new_versioned(
|
||||
addr conn,
|
||||
unsafeAddr source,
|
||||
unsafeAddr id,
|
||||
path.toPathPtr,
|
||||
CurrentQuicVersion,
|
||||
NGTCP2_CALLBACKS_V1,
|
||||
addr callbacks,
|
||||
NGTCP2_SETTINGS_V2,
|
||||
addr settings,
|
||||
NGTCP2_TRANSPORT_PARAMS_V1,
|
||||
addr transportParams,
|
||||
nil,
|
||||
addr result[],
|
||||
)
|
||||
let cptls: ptr ngtcp2_crypto_picotls_ctx = create(ngtcp2_crypto_picotls_ctx)
|
||||
|
||||
result.conn = Opt.some(conn)
|
||||
ngtcp2_crypto_picotls_ctx_init(cptls)
|
||||
|
||||
var tls = tlsContext.newConnection(true)
|
||||
cptls.ptls = tls.conn
|
||||
|
||||
var addExtensions = cast[ptr UncheckedArray[ptls_raw_extension_t]](alloc(
|
||||
ptls_raw_extension_t.sizeof * 2
|
||||
))
|
||||
addExtensions[0] = ptls_raw_extension_t(type_field: high(uint16))
|
||||
addExtensions[1] = ptls_raw_extension_t(type_field: high(uint16))
|
||||
cptls.handshake_properties = ptls_handshake_properties_t(
|
||||
additional_extensions: cast[ptr ptls_raw_extension_t](addExtensions)
|
||||
)
|
||||
|
||||
ngtcp2_conn_set_tls_native_handle(conn, cptls)
|
||||
|
||||
var connref = create(ngtcp2_crypto_conn_ref)
|
||||
connref.user_data = conn
|
||||
connref.get_conn = proc(
|
||||
connRef: ptr ngtcp2_crypto_conn_ref
|
||||
): ptr ngtcp2_conn {.cdecl.} =
|
||||
cast[ptr ngtcp2_conn](connRef.user_data)
|
||||
|
||||
var dataPtr = ptls_get_data_ptr(tls.conn)
|
||||
dataPtr[] = connref
|
||||
|
||||
ret = ngtcp2_crypto_picotls_configure_server_session(cptls)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not configure server session: " & $ret)
|
||||
|
||||
nConn.conn = Opt.some(conn)
|
||||
nConn.tlsConn = tls
|
||||
nConn.cptls = cptls
|
||||
nConn.connref = connref
|
||||
nConn
|
||||
|
||||
proc extractIds(datagram: openArray[byte]): tuple[source, dest: ngtcp2_cid] =
|
||||
let info = parseDatagram(datagram)
|
||||
(source: info.source.toCid, dest: info.destination.toCid)
|
||||
|
||||
proc newNgtcp2Server*(
|
||||
local, remote: TransportAddress, datagram: openArray[byte]
|
||||
tlsContext: PicoTLSContext, local, remote: TransportAddress, datagram: openArray[byte]
|
||||
): Ngtcp2Connection =
|
||||
let (source, destination) = extractIds(datagram)
|
||||
newNgtcp2Server(local, remote, source, destination)
|
||||
newNgtcp2Server(tlsContext, local, remote, source, destination)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
|
||||
proc defaultSettings*(): ngtcp2_settings =
|
||||
ngtcp2_settings_default_versioned(NGTCP2_SETTINGS_V2, addr result)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import pkg/ngtcp2
|
||||
import ngtcp2
|
||||
import ../../../helpers/openarray
|
||||
import ../../stream
|
||||
import ../stream/openstate
|
||||
import ./connection
|
||||
import chronicles
|
||||
|
||||
proc newStream(connection: Ngtcp2Connection, id: int64): Stream =
|
||||
newStream(id, newOpenStream(connection))
|
||||
@@ -29,6 +30,7 @@ proc onStreamClose(
|
||||
user_data: pointer,
|
||||
stream_user_data: pointer,
|
||||
): cint {.cdecl.} =
|
||||
trace "onStreamClose"
|
||||
let openStream = cast[OpenStream](stream_user_data)
|
||||
if openStream != nil:
|
||||
openStream.onClose()
|
||||
@@ -39,16 +41,43 @@ proc onReceiveStreamData(
|
||||
stream_id: int64,
|
||||
offset: uint64,
|
||||
data: ptr uint8,
|
||||
datalen: uint,
|
||||
datalen: csize_t,
|
||||
user_data: pointer,
|
||||
stream_user_data: pointer,
|
||||
): cint {.cdecl.} =
|
||||
trace "onReceiveStreamData"
|
||||
let state = cast[OpenStream](stream_user_data)
|
||||
var bytes = newSeqUninitialized[byte](datalen)
|
||||
copyMem(bytes.toUnsafePtr, data, datalen)
|
||||
state.receive(bytes)
|
||||
|
||||
proc onStreamReset(
|
||||
connection: ptr ngtcp2_conn,
|
||||
stream_id: int64,
|
||||
final_size: uint64,
|
||||
app_error_code: uint64,
|
||||
user_data: pointer,
|
||||
stream_user_data: pointer,
|
||||
): cint {.cdecl.} =
|
||||
trace "onStreamReset"
|
||||
let openStream = cast[OpenStream](stream_user_data)
|
||||
if openStream != nil:
|
||||
openStream.onClose()
|
||||
return 0
|
||||
|
||||
proc onStreamStopSending(
|
||||
conn: ptr ngtcp2_conn,
|
||||
stream_id: int64,
|
||||
app_error_code: uint64,
|
||||
user_data: pointer,
|
||||
stream_user_data: pointer,
|
||||
): cint {.cdecl.} =
|
||||
trace "onStreamStopSending"
|
||||
return 0
|
||||
|
||||
proc installStreamCallbacks*(callbacks: var ngtcp2_callbacks) =
|
||||
callbacks.stream_open = onStreamOpen
|
||||
callbacks.stream_close = onStreamClose
|
||||
callbacks.recv_stream_data = onReceiveStreamData
|
||||
callbacks.stream_reset = onStreamReset
|
||||
callbacks.stream_stop_sending = onStreamStopSending
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import ../../../basics
|
||||
import ../../stream
|
||||
import chronicles
|
||||
|
||||
logScope:
|
||||
topics = "closed state"
|
||||
|
||||
type
|
||||
ClosedStream* = ref object of StreamState
|
||||
@@ -15,9 +19,11 @@ method enter*(state: ClosedStream, stream: Stream) =
|
||||
stream.closed.fire()
|
||||
|
||||
method read*(state: ClosedStream): Future[seq[byte]] {.async.} =
|
||||
trace "cant read, stream is closed"
|
||||
raise newException(ClosedStreamError, "stream is closed")
|
||||
|
||||
method write*(state: ClosedStream, bytes: seq[byte]) {.async.} =
|
||||
trace "cant write, stream is closed"
|
||||
raise newException(ClosedStreamError, "stream is closed")
|
||||
|
||||
method close*(state: ClosedStream) {.async.} =
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import ../basics
|
||||
import ./tlsbackend
|
||||
import ./quicconnection
|
||||
import ./ngtcp2/connection/openstate
|
||||
|
||||
proc newQuicClientConnection*(local, remote: TransportAddress): QuicConnection =
|
||||
newQuicConnection(openClientConnection(local, remote))
|
||||
proc newQuicClientConnection*(
|
||||
tlsBackend: TLSBackend, local, remote: TransportAddress
|
||||
): QuicConnection =
|
||||
let openConn = openClientConnection(tlsBackend, local, remote)
|
||||
newQuicConnection(openConn)
|
||||
|
||||
proc newQuicServerConnection*(
|
||||
local, remote: TransportAddress, datagram: Datagram
|
||||
tlsBackend: TLSBackend, local, remote: TransportAddress, datagram: Datagram
|
||||
): QuicConnection =
|
||||
newQuicConnection(openServerConnection(local, remote, datagram))
|
||||
let openConn = openServerConnection(tlsBackend, local, remote, datagram)
|
||||
newQuicConnection(openConn)
|
||||
|
||||
@@ -12,7 +12,7 @@ type
|
||||
|
||||
StreamError* = object of QuicError
|
||||
|
||||
{.push locks: "unknown", raises: [QuicError].}
|
||||
{.push raises: [QuicError].}
|
||||
|
||||
method enter*(state: StreamState, stream: Stream) {.base.} =
|
||||
doAssert not state.entered # states are not reentrant
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ../basics
|
||||
import chronicles
|
||||
|
||||
type Timeout* = ref object
|
||||
timer: Opt[TimerCallback]
|
||||
@@ -6,6 +7,7 @@ type Timeout* = ref object
|
||||
expired: AsyncEvent
|
||||
|
||||
proc setTimer(timeout: Timeout, moment: Moment) =
|
||||
trace "setTimer"
|
||||
proc onTimeout(_: pointer) =
|
||||
timeout.expired.fire()
|
||||
timeout.onExpiry()
|
||||
@@ -16,6 +18,7 @@ const skip = proc() =
|
||||
discard
|
||||
|
||||
proc newTimeout*(onExpiry: proc() {.gcsafe, raises: [].} = skip): Timeout =
|
||||
trace "newTimeout"
|
||||
Timeout(onExpiry: onExpiry, expired: newAsyncEvent())
|
||||
|
||||
proc stop*(timeout: Timeout) =
|
||||
@@ -32,3 +35,4 @@ proc set*(timeout: Timeout, duration: Duration) =
|
||||
|
||||
proc expired*(timeout: Timeout) {.async.} =
|
||||
await timeout.expired.wait()
|
||||
trace "expired"
|
||||
|
||||
45
quic/transport/tlsbackend.nim
Normal file
45
quic/transport/tlsbackend.nim
Normal file
@@ -0,0 +1,45 @@
|
||||
import results
|
||||
import ngtcp2
|
||||
import ./ngtcp2/native
|
||||
import ../errors
|
||||
|
||||
export CertificateVerifier
|
||||
export certificateVerifierCB
|
||||
export CustomCertificateVerifier
|
||||
export InsecureCertificateVerifier
|
||||
export init
|
||||
export destroy
|
||||
|
||||
type TLSBackend* = ref object
|
||||
picoTLS*: PicoTLSContext
|
||||
|
||||
proc newServerTLSBackend*(
|
||||
certificate: seq[byte],
|
||||
key: seq[byte],
|
||||
certificateVerifier: Opt[CertificateVerifier],
|
||||
): TLSBackend {.raises: [QuicError].} =
|
||||
let picotlsCtx = PicoTLSContext.init(
|
||||
certificate, key, certificateVerifier, certificateVerifier.isSome
|
||||
)
|
||||
let ret = ngtcp2_crypto_picotls_configure_server_context(picotlsCtx.context)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not configure server context: " & $ret)
|
||||
return TLSBackend(picoTLS: picotlsCtx)
|
||||
|
||||
proc newClientTLSBackend*(
|
||||
certificate: seq[byte],
|
||||
key: seq[byte],
|
||||
certificateVerifier: Opt[CertificateVerifier],
|
||||
): TLSBackend {.raises: [QuicError].} =
|
||||
let picotlsCtx = PicoTLSContext.init(certificate, key, certificateVerifier, false)
|
||||
let ret = ngtcp2_crypto_picotls_configure_client_context(picotlsCtx.context)
|
||||
if ret != 0:
|
||||
raise newException(QuicError, "could not configure client context: " & $ret)
|
||||
return TLSBackend(picoTLS: picotlsCtx)
|
||||
|
||||
proc destroy*(self: TLSBackend) =
|
||||
if self.picoTLS.isNil:
|
||||
return
|
||||
|
||||
self.picoTLS.destroy()
|
||||
self.picoTLS = nil
|
||||
@@ -1,4 +1,3 @@
|
||||
from pkg/ngtcp2 import NGTCP2_PROTO_VER_V2
|
||||
#from pkg/ngtcp2 import NGTCP2_PROTO_VER_V2
|
||||
|
||||
const DraftVersion29 = 0xFF00001D'u32
|
||||
const CurrentQuicVersion* = cast[uint32](NGTCP2_PROTO_VER_V2)
|
||||
const CurrentQuicVersion* = cast[uint32](0x6b3343cfu) # TODO: figure out how to export NGTCP2_PROTO_VER_V2 with futhark
|
||||
|
||||
15
tests/helpers/certificate.nim
Normal file
15
tests/helpers/certificate.nim
Normal file
@@ -0,0 +1,15 @@
|
||||
import sequtils
|
||||
import os
|
||||
|
||||
const certificateStr =
|
||||
staticRead(parentDir(currentSourcePath()) / "testCertificate.pem")
|
||||
const privateKeyStr = staticRead(parentDir(currentSourcePath()) / "testPrivateKey.pem")
|
||||
|
||||
proc strToSeq(val: string): seq[byte] =
|
||||
toSeq(val.toOpenArrayByte(0, val.high))
|
||||
|
||||
proc testCertificate*(): seq[byte] =
|
||||
strToSeq(certificateStr)
|
||||
|
||||
proc testPrivateKey*(): seq[byte] =
|
||||
strToSeq(privateKeyStr)
|
||||
@@ -1,8 +1,8 @@
|
||||
import std/random
|
||||
import pkg/chronos
|
||||
import pkg/quic/transport/quicconnection
|
||||
import pkg/quic/transport/quicclientserver
|
||||
import pkg/quic/transport/[quicconnection, quicclientserver, tlsbackend]
|
||||
import pkg/quic/helpers/asyncloop
|
||||
import ./certificate
|
||||
import ./addresses
|
||||
|
||||
proc networkLoop*(source, destination: QuicConnection) {.async.} =
|
||||
@@ -37,11 +37,17 @@ proc simulateLossyNetwork*(a, b: QuicConnection) {.async.} =
|
||||
await allFutures(loop1.cancelAndWait(), loop2.cancelAndWait())
|
||||
|
||||
proc setupConnection*(): Future[tuple[client, server: QuicConnection]] {.async.} =
|
||||
let client = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
client.send()
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let client = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
|
||||
client.send() # Start Handshake
|
||||
let datagram = await client.outgoing.get()
|
||||
let server = newQuicServerConnection(zeroAddress, zeroAddress, datagram)
|
||||
server.receive(datagram)
|
||||
let serverTLSBackend = newServerTLSBackend(
|
||||
testCertificate(), testPrivateKey(), Opt.none(CertificateVerifier)
|
||||
)
|
||||
let server =
|
||||
newQuicServerConnection(serverTLSBackend, zeroAddress, zeroAddress, datagram)
|
||||
|
||||
result = (client, server)
|
||||
|
||||
proc performHandshake*(): Future[tuple[client, server: QuicConnection]] {.async.} =
|
||||
|
||||
12
tests/helpers/testCertificate.pem
Normal file
12
tests/helpers/testCertificate.pem
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIB0jCCAXegAwIBAgIVAJUFhCQrlzzkFZbZkyXNRwCXSvYWMAwGCCqGSM49BAMC
|
||||
BQAwFDESMBAGA1UEAwwJbGlicDJwLmlvMCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYw
|
||||
MTAxMDAwMDAwWjAUMRIwEAYDVQQDDAlsaWJwMnAuaW8wWTATBgcqhkjOPQIBBggq
|
||||
hkjOPQMBBwNCAAThQpOzDszrSDCf9wqR1+0WOt6BHVwv3Q3R+8quylRwLbR2D8ov
|
||||
PoXfWCzoACju9j2wBj4WHgb69Cpp9FiW7qx0o4GhMIGeMIGABgorBgEEAYOiWgEB
|
||||
BHIwbwQlCAISIQIz55rVQB21ks44HQ9G/0YJgvDB5v5/6Bl+MkQq5k3A4gRGMEQC
|
||||
IBLxYkAwI5H5GMzWPpjhOt3pJQ9gi7ICqCejEIFSuvf1AiA2kZZ+twfvyIYV7AAv
|
||||
InM3WOWZlgY4a7TDRm8C974pHwAwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAw
|
||||
DAYIKoZIzj0EAwIFAANHADBEAiBV/KbtQ5SIAu373/8j/PzLnewCHUzXWTd3zjN9
|
||||
nzkGIgIgd1IJKDqnotzvCKw4WYrAmgTMkINJwlaQf278ranRaEY=
|
||||
-----END CERTIFICATE-----
|
||||
5
tests/helpers/testPrivateKey.pem
Normal file
5
tests/helpers/testPrivateKey.pem
Normal file
@@ -0,0 +1,5 @@
|
||||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIJTCwOljI3BRUBE3VXVy4sN16sh62IliV7X9y3uEPh4FoAoGCCqGSM49
|
||||
AwEHoUQDQgAE4UKTsw7M60gwn/cKkdftFjregR1cL90N0fvKrspUcC20dg/KLz6F
|
||||
31gs6AAo7vY9sAY+Fh4G+vQqafRYlu6sdA==
|
||||
-----END EC PRIVATE KEY-----
|
||||
@@ -1,17 +1,23 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/unittest2/asynctests
|
||||
import pkg/quic
|
||||
import chronos
|
||||
import chronos/unittest2/asynctests
|
||||
import quic
|
||||
import quic/transport/tlsbackend
|
||||
import ../helpers/certificate
|
||||
|
||||
suite "api":
|
||||
setup:
|
||||
var listener = listen(initTAddress("127.0.0.1:0"))
|
||||
let serverTLSConfig = TLSConfig.init(testCertificate(), testPrivateKey())
|
||||
var server = QuicServer.init(serverTLSConfig)
|
||||
let clientTLSConfig = TLSConfig.init()
|
||||
var client = QuicClient.init(clientTLSConfig)
|
||||
var listener = server.listen(initTAddress("127.0.0.1:0"))
|
||||
let address = listener.localAddress
|
||||
|
||||
teardown:
|
||||
waitFor listener.stop()
|
||||
|
||||
asyncTest "opens and drops connections":
|
||||
let dialing = dial(address)
|
||||
let dialing = client.dial(address)
|
||||
let accepting = listener.accept()
|
||||
|
||||
let outgoing = await dialing
|
||||
@@ -26,7 +32,7 @@ suite "api":
|
||||
await incoming.drop()
|
||||
|
||||
asyncTest "opens and closes streams":
|
||||
let dialing = dial(address)
|
||||
let dialing = client.dial(address)
|
||||
let accepting = listener.accept()
|
||||
|
||||
let outgoing = await dialing
|
||||
@@ -42,7 +48,7 @@ suite "api":
|
||||
await incoming.drop()
|
||||
|
||||
asyncTest "waits until peer closes connection":
|
||||
let dialing = dial(address)
|
||||
let dialing = client.dial(address)
|
||||
let accepting = listener.accept()
|
||||
|
||||
let outgoing = await dialing
|
||||
@@ -53,11 +59,11 @@ suite "api":
|
||||
|
||||
asyncTest "accepts multiple incoming connections":
|
||||
let accepting1 = listener.accept()
|
||||
let outgoing1 = await dial(address)
|
||||
let outgoing1 = await client.dial(address)
|
||||
let incoming1 = await accepting1
|
||||
|
||||
let accepting2 = listener.accept()
|
||||
let outgoing2 = await dial(address)
|
||||
let outgoing2 = await client.dial(address)
|
||||
let incoming2 = await accepting2
|
||||
|
||||
check incoming1 != incoming2
|
||||
@@ -70,7 +76,7 @@ suite "api":
|
||||
asyncTest "writes to and reads from streams":
|
||||
let message = @[1'u8, 2'u8, 3'u8]
|
||||
|
||||
let outgoing = await dial(address)
|
||||
let outgoing = await client.dial(address)
|
||||
defer:
|
||||
await outgoing.drop()
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/unittest2/asynctests
|
||||
import pkg/quic/connection
|
||||
import chronos
|
||||
import chronos/unittest2/asynctests
|
||||
import results
|
||||
|
||||
import quic/connection
|
||||
import quic/transport/tlsbackend
|
||||
import ../helpers/udp
|
||||
|
||||
suite "connections":
|
||||
@@ -9,8 +12,8 @@ suite "connections":
|
||||
|
||||
asyncTest "handles error when writing to udp transport by closing connection":
|
||||
let udp = newDatagramTransport()
|
||||
let connection = newOutgoingConnection(udp, address)
|
||||
|
||||
let tlsBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let connection = newOutgoingConnection(tlsBackend, udp, address)
|
||||
await udp.closeWait()
|
||||
connection.startHandshake()
|
||||
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
import results
|
||||
import pkg/unittest2
|
||||
import ../helpers/async
|
||||
import pkg/quic
|
||||
import pkg/chronos
|
||||
import ../helpers/certificate
|
||||
|
||||
suite "examples from Readme":
|
||||
test "outgoing and incoming connections":
|
||||
let message = cast[seq[byte]]("some message")
|
||||
proc outgoing() {.async.} =
|
||||
let connection = await dial(initTAddress("127.0.0.1:12345"))
|
||||
let cb = proc(derCertificates: seq[seq[byte]]): bool {.gcsafe.} =
|
||||
# TODO: implement custom certificate validation
|
||||
return derCertificates.len > 0
|
||||
|
||||
let customCertVerif: CertificateVerifier = CustomCertificateVerifier.init(cb)
|
||||
let tlsConfig = TLSConfig.init(certificateVerifier = Opt.some(customCertVerif))
|
||||
let client = QuicClient.init(tlsConfig)
|
||||
let connection = await client.dial(initTAddress("127.0.0.1:12345"))
|
||||
let stream = await connection.openStream()
|
||||
let message = cast[seq[byte]]("some message")
|
||||
await stream.write(message)
|
||||
await stream.close()
|
||||
await connection.waitClosed()
|
||||
await connection.close()
|
||||
|
||||
proc incoming() {.async.} =
|
||||
let listener = listen(initTAddress("127.0.0.1:12345"))
|
||||
let tlsConfig = TLSConfig.init(testCertificate(), testPrivateKey())
|
||||
let server = QuicServer.init(tlsConfig)
|
||||
let listener = server.listen(initTAddress("127.0.0.1:12345"))
|
||||
|
||||
let connection = await listener.accept()
|
||||
let stream = await connection.incomingStream()
|
||||
let message = await stream.read()
|
||||
let readMessage = await stream.read()
|
||||
await stream.close()
|
||||
await connection.close()
|
||||
await connection.waitClosed()
|
||||
await listener.stop()
|
||||
|
||||
check message == cast[seq[byte]]("some message")
|
||||
listener.destroy()
|
||||
check readMessage == message
|
||||
|
||||
waitFor allSucceeded(incoming(), outgoing())
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/unittest2/asynctests
|
||||
import pkg/quic
|
||||
import pkg/quic/listener
|
||||
import chronos
|
||||
import chronos/unittest2/asynctests
|
||||
import quic
|
||||
import quic/listener
|
||||
import quic/transport/tlsbackend
|
||||
import ../helpers/udp
|
||||
import ../helpers/certificate
|
||||
|
||||
suite "listener":
|
||||
setup:
|
||||
var listener = newListener(initTAddress("127.0.0.1:0"))
|
||||
let tlsBackend = newServerTLSBackend(testCertificate(), testPrivateKey(), Opt.none(CertificateVerifier))
|
||||
var listener = newListener(tlsBackend, initTAddress("127.0.0.1:0"))
|
||||
let address = listener.localAddress
|
||||
|
||||
check address.port != Port(0)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import pkg/unittest2
|
||||
import pkg/ngtcp2
|
||||
import pkg/stew/results
|
||||
import pkg/quic/errors
|
||||
import pkg/quic/transport/ngtcp2/native/connection
|
||||
import pkg/quic/transport/ngtcp2/native/client
|
||||
import pkg/quic/transport/ngtcp2/native/params
|
||||
import pkg/quic/transport/ngtcp2/native/settings
|
||||
import unittest2
|
||||
import ngtcp2
|
||||
import results
|
||||
import quic/errors
|
||||
import quic/transport/tlsbackend
|
||||
import quic/transport/ngtcp2/native/[connection, client, params, settings]
|
||||
import ../helpers/addresses
|
||||
|
||||
suite "ngtcp2 transport parameters":
|
||||
@@ -17,7 +15,15 @@ suite "ngtcp2 transport parameters":
|
||||
test "encoding and decoding":
|
||||
let encoded = encodeTransportParameters(transport_params)
|
||||
let decoded = decodeTransportParameters(encoded)
|
||||
check decoded == transport_params
|
||||
check:
|
||||
transport_params.initial_max_streams_uni == decoded.initial_max_streams_uni
|
||||
transport_params.initial_max_stream_data_uni == decoded.initial_max_stream_data_uni
|
||||
transport_params.initial_max_streams_bidi == decoded.initial_max_streams_bidi
|
||||
transport_params.initial_max_stream_data_bidi_local ==
|
||||
decoded.initial_max_stream_data_bidi_local
|
||||
transport_params.initial_max_stream_data_bidi_remote ==
|
||||
decoded.initial_max_stream_data_bidi_remote
|
||||
transport_params.initial_max_data == decoded.initial_max_data
|
||||
|
||||
test "raises when decoding fails":
|
||||
var encoded = encodeTransportParameters(transport_params)
|
||||
@@ -27,7 +33,8 @@ suite "ngtcp2 transport parameters":
|
||||
discard decodeTransportParameters(encoded)
|
||||
|
||||
test "raises when setting remote parameters fails":
|
||||
let connection = newNgtcp2Client(zeroAddress, zeroAddress)
|
||||
let tlsBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let connection = newNgtcp2Client(tlsBackend.picoTLS, zeroAddress, zeroAddress)
|
||||
defer:
|
||||
connection.destroy()
|
||||
transport_params.active_connection_id_limit = 0
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import pkg/chronos
|
||||
import pkg/chronos/unittest2/asynctests
|
||||
import pkg/quic/errors
|
||||
import pkg/quic/transport/quicconnection
|
||||
import pkg/quic/transport/quicclientserver
|
||||
import pkg/quic/udp/datagram
|
||||
import pkg/quic/transport/connectionid
|
||||
import chronos
|
||||
import chronos/unittest2/asynctests
|
||||
import quic/errors
|
||||
import quic/transport/[quicconnection, quicclientserver, tlsbackend]
|
||||
import quic/udp/datagram
|
||||
import quic/transport/connectionid
|
||||
import ../helpers/simulation
|
||||
import ../helpers/addresses
|
||||
import ../helpers/certificate
|
||||
|
||||
suite "quic connection":
|
||||
asyncTest "sends outgoing datagrams":
|
||||
let client = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let client = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
defer:
|
||||
await client.drop()
|
||||
client.send()
|
||||
@@ -18,14 +19,17 @@ suite "quic connection":
|
||||
check datagram.len > 0
|
||||
|
||||
asyncTest "processes received datagrams":
|
||||
let client = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let client = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
defer:
|
||||
await client.drop()
|
||||
|
||||
client.send()
|
||||
let datagram = await client.outgoing.get()
|
||||
|
||||
let server = newQuicServerConnection(zeroAddress, zeroAddress, datagram)
|
||||
let serverTLSBackend = newServerTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let server =
|
||||
newQuicServerConnection(serverTLSBackend, zeroAddress, zeroAddress, datagram)
|
||||
defer:
|
||||
await server.drop()
|
||||
|
||||
@@ -35,7 +39,10 @@ suite "quic connection":
|
||||
let invalid = Datagram(data: @[0'u8])
|
||||
|
||||
expect QuicError:
|
||||
discard newQuicServerConnection(zeroAddress, zeroAddress, invalid)
|
||||
let serverTLSBackend =
|
||||
newServerTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
discard
|
||||
newQuicServerConnection(serverTLSBackend, zeroAddress, zeroAddress, invalid)
|
||||
|
||||
asyncTest "performs handshake":
|
||||
let (client, server) = await performHandshake()
|
||||
@@ -60,11 +67,16 @@ suite "quic connection":
|
||||
check server.ids != client.ids
|
||||
|
||||
asyncTest "notifies about id changes":
|
||||
let client = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let client = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
client.send()
|
||||
let datagram = await client.outgoing.get()
|
||||
|
||||
let server = newQuicServerConnection(zeroAddress, zeroAddress, datagram)
|
||||
let serverTLSBackend = newServerTLSBackend(
|
||||
testCertificate(), testPrivateKey(), Opt.none(CertificateVerifier)
|
||||
)
|
||||
let server =
|
||||
newQuicServerConnection(serverTLSBackend, zeroAddress, zeroAddress, datagram)
|
||||
var newId: ConnectionId
|
||||
server.onNewId = proc(id: ConnectionId) =
|
||||
newId = id
|
||||
@@ -80,7 +92,8 @@ suite "quic connection":
|
||||
await server.drop
|
||||
|
||||
asyncTest "raises ConnectionError when closed":
|
||||
let connection = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let connection = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
await connection.drop()
|
||||
|
||||
expect ConnectionError:
|
||||
@@ -93,7 +106,8 @@ suite "quic connection":
|
||||
discard await connection.openStream()
|
||||
|
||||
asyncTest "has empty list of ids when closed":
|
||||
let connection = newQuicClientConnection(zeroAddress, zeroAddress)
|
||||
let clientTLSBackend = newClientTLSBackend(@[], @[], Opt.none(CertificateVerifier))
|
||||
let connection = newQuicClientConnection(clientTLSBackend, zeroAddress, zeroAddress)
|
||||
await connection.drop()
|
||||
|
||||
check connection.ids.len == 0
|
||||
|
||||
@@ -33,13 +33,6 @@ suite "streams":
|
||||
let stream = await client.openStream()
|
||||
await stream.close()
|
||||
|
||||
asyncTest "writes to stream":
|
||||
let stream = await client.openStream()
|
||||
let message = @[1'u8, 2'u8, 3'u8]
|
||||
await stream.write(message)
|
||||
|
||||
check client.outgoing.anyIt(it.data.contains(message))
|
||||
|
||||
asyncTest "writes zero-length message":
|
||||
let stream = await client.openStream()
|
||||
await stream.write(@[])
|
||||
|
||||
Reference in New Issue
Block a user