A peer-to-peer prototype application using QUIC
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
3.3 KiB

import asyncdispatch, asyncnet, message, net, tables, random, strformat
Endpoint* = tuple[hostname: string, port: Port]
ServerConnection* = ref object
sock: AsyncSocket
outMessages: TableRef[string, Future[string]]
peerNotifications*: FutureStream[string]
probedIp*: IpAddress
probedSrcPorts*: seq[Port]
ServerError* = object of ValueError
OkGetPeerinfo* = object
ip*: IpAddress
localPort*: Port
probedPorts*: seq[Port]
OkGetEndpoint* = object
ip*: IpAddress
port*: Port
NotifyPeer* = object
sender*: string
recipient*: string
srcIp*: IpAddress
srcPort*: Port
probedSrcPorts*: seq[Port]
dstIp*: IpAddress
dstPort*: Port
probedDstPorts*: seq[Port]
proc getEndpoint(sock: AsyncSocket, serverHostname: string, serverPort: Port):
Future[OkGetEndpoint] {.async.} =
# TODO: use sock (UDP socket) for probing
let tcpSock = newAsyncSocket()
let (_, srcPort) = sock.getLocalAddr
var failCount = 0
while true:
except OSError as e:
if failCount == 3:
raise e
await sleepAsync(100)
await tcpSock.connect(serverHostname, serverPort)
let id = rand(uint32)
await tcpSock.send(&"get-endpoint|{id}\n")
let line = await tcpSock.recvLine(maxLength = 400)
let args = line.parseArgs(3)
assert(args[0] == "ok")
assert(args[1] == $id)
result = parseMessage[OkGetEndpoint](args[2])
let emptyLine = await tcpSock.recvLine(maxLength = 400)
assert(emptyLine.len == 0)
proc initServerConnection*(sock: AsyncSocket, serverHostname: string,
serverPort: Port, probingServers: seq[Endpoint]):
Future[ServerConnection] {.async.} =
let peerNotifications = newFutureStream[string]("initServerConnection")
result = ServerConnection(outMessages: newTable[string, Future[string]](),
peerNotifications: peerNotifications)
for s in probingServers:
let endpoint = await getEndpoint(sock, s.hostname, s.port)
# FIXME: what if we get get different IPs from different servers
result.probedIp = endpoint.ip
result.sock = await asyncnet.dial(serverHostname,
proc handleServerMessages*(conn: ServerConnection) {.async.} =
while true:
let line = await conn.sock.recvLine(maxLength = 400)
let args = line.parseArgs(3, 1)
case args[0]:
of "ok":
let future = conn.outMessages[args[1]]
of "error":
let future = conn.outMessages[args[1]]
future.fail(newException(ServerError, args[2]))
of "notify-peer":
asyncCheck conn.peerNotifications.write(line.substr(args[0].len + 1))
raise newException(ValueError, "invalid server message")
proc sendRequest*(connection: ServerConnection, command: string,
content: string): Future[string] =
result = newFuture[string]("sendRequest")
let id = $rand(uint32)
var request: string
if content.len != 0:
request = &"{command}|{id}|{content}\n"
request = &"{command}|{id}\n"
asyncCheck connection.sock.send(request)
connection.outMessages[id] = result