nim-webrtc/webrtc/stun/stun_transport.nim

121 lines
3.7 KiB
Nim

# Nim-WebRTC
# Copyright (c) 2024 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 tables, sequtils
import chronos, chronicles, bearssl
import stun_connection, stun_message, ../udp_transport
logScope:
topics = "webrtc stun stun_transport"
const
StunTransportTracker* = "webrtc.stun.transport"
StunMaxPendingConnections = 512
type
Stun* = ref object
connections: Table[TransportAddress, StunConn]
pendingConn: AsyncQueue[StunConn]
readingLoop: Future[void].Raising([CancelledError])
udp: UdpTransport
laddr*: TransportAddress
usernameProvider: StunUsernameProvider
usernameChecker: StunUsernameChecker
passwordProvider: StunPasswordProvider
rng: ref HmacDrbgContext
proc addConnToTable(self: Stun, conn: StunConn) =
proc cleanup() =
self.connections.del(conn.raddr)
self.connections[conn.raddr] = conn
conn.addOnClose(cleanup)
proc accept*(self: Stun): Future[StunConn] {.async: (raises: [CancelledError]).} =
## Accept a Stun Connection
##
var res: StunConn
while true:
res = await self.pendingConn.popFirst()
if res.closed != true: # The connection could be closed before being accepted
break
return res
proc connect*(
self: Stun,
raddr: TransportAddress
): Future[StunConn] {.async: (raises: []).} =
## Connect to a remote address, creating a Stun Connection
##
self.connections.withValue(raddr, res):
return res[]
do:
let res = StunConn.new(self.udp, raddr, false, self.usernameProvider,
self.usernameChecker, self.passwordProvider, self.rng)
self.addConnToTable(res)
return res
proc stunReadLoop(self: Stun) {.async: (raises: [CancelledError]).} =
while true:
let (buf, raddr) = await self.udp.read()
var stunConn: StunConn
if not self.connections.hasKey(raddr):
stunConn = StunConn.new(self.udp, raddr, true, self.usernameProvider,
self.usernameChecker, self.passwordProvider, self.rng)
self.addConnToTable(stunConn)
await self.pendingConn.addLast(stunConn)
else:
try:
stunConn = self.connections[raddr]
except KeyError as exc:
doAssert(false, "Should never happen")
if isStunMessage(buf):
await stunConn.stunMsgs.addLast(buf)
else:
await stunConn.dataRecv.addLast(buf)
proc stop*(self: Stun) {.async: (raises: []).} =
## Stop the Stun transport and close all the connections
##
try:
await allFutures(toSeq(self.connections.values()).mapIt(it.close()))
except CancelledError as exc:
discard
await self.readingLoop.cancelAndWait()
untrackCounter(StunTransportTracker)
proc defaultUsernameProvider(): string = ""
proc defaultUsernameChecker(username: seq[byte]): bool = true
proc defaultPasswordProvider(username: seq[byte]): seq[byte] = @[]
proc new*(
T: type Stun,
udp: UdpTransport,
usernameProvider: StunUsernameProvider = defaultUsernameProvider,
usernameChecker: StunUsernameChecker = defaultUsernameChecker,
passwordProvider: StunPasswordProvider = defaultPasswordProvider,
rng: ref HmacDrbgContext = HmacDrbgContext.new(),
): T =
## Initialize the Stun transport
##
var self = T(
udp: udp,
laddr: udp.laddr,
usernameProvider: usernameProvider,
usernameChecker: usernameChecker,
passwordProvider: passwordProvider,
rng: rng
)
self.readingLoop = self.stunReadLoop()
self.pendingConn = newAsyncQueue[StunConn](StunMaxPendingConnections)
trackCounter(StunTransportTracker)
return self