Last active
October 31, 2020 13:38
-
-
Save demotomohiro/c9d9ad7e17639c6acb9bd2be7c204f3f to your computer and use it in GitHub Desktop.
Nim version of libnice example code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Nim version of example code of libnice: | |
# https://libnice.freedesktop.org | |
# | |
# Refer libnice/examples/simple-example.c | |
# Push enter key at same time as possible when you enter remote data. | |
# | |
# Public STUN server list: | |
# https://gist.github.com/mondain/b0ec1cf5f60ae726202e | |
import gintro/[nice, gio, glib, gobject] | |
import os, strformat, strutils, nativesockets, net, strscans | |
# See https://gitlab.gnome.org/GNOME/glib/-/blob/master/glib/gslist.h | |
type GSList {.pure.} = object | |
data: pointer | |
next: ptr GSList | |
var | |
gloop: MainLoop | |
ioStdin: IOChannel | |
streamId: int | |
iterator items(list: ptr GSList): pointer = | |
var item = list | |
while item != nil: | |
yield item.data | |
item = item.next | |
proc toStringVal(s: string): Value = | |
let gtype = typeFromName("gchararray") | |
discard init(result, gtype) | |
setString(result, s) | |
proc toUIntVal(i: int): Value = | |
let gtype = typeFromName("guint") | |
discard init(result, gtype) | |
setUint(result, i) | |
proc toBoolVal(b: bool): Value = | |
let gtype = typeFromName("gboolean") | |
discard init(result, gtype) | |
setBoolean(result, b) | |
proc upCast[T: object, U](gobj: ptr T): U = | |
static: | |
assert T is gobject.Object00 | |
let qdata = g_object_get_qdata(gobj, Quark) | |
assert qdata != nil | |
result = cast[type(result)](qdata) | |
assert(result.impl == gobj) | |
proc toAgent(src: ptr Agent00): Agent = upCast[Agent00, Agent](src) | |
proc port(niceAddr: NiceAddress): Port = | |
if niceAddr.`addr`.sa_family.cint == toInt(AF_INET): | |
result = ntohs(niceAddr.ip4.sin_port).Port | |
elif niceAddr.`addr`.sa_family.cint == toInt(AF_INET6): | |
result = ntohs(niceAddr.ip6.sin6_port).Port | |
else: | |
raise newException(ValueError, "Neither IPv4 nor IPv6") | |
proc addrString(niceAddr: NiceAddress): string = | |
getAddrString(unsafeAddr niceAddr.`addr`) | |
proc printLocalData(agent: Agent; streamId: uint32; component_id: uint32) = | |
var localUfrag, localPassword: string | |
assert agent.localCredentials(streamId.int, localUfrag, localPassword) | |
stdout.write localUfrag, ' ', localPassword | |
let cands = cast[ptr GSList](agent.localCandidates(streamId.int, componentId.int)) | |
assert cands != nil | |
for data in cands: | |
let | |
c = cast[ptr NiceCandidate](data) | |
stdout.write &" {c.foundation.addr.cstring},{c.priority},{c.`addr`.addrString},{c.`addr`.port},{c.`type`}" | |
stdout.write '\n' | |
proc cbCandidateGatheringDone(self: ptr Agent00; streamId: uint32; data: pointer) {.cdecl.} = | |
var agent = self.toAgent | |
echo "Copy this line to remote client:" | |
stdout.write "\n " | |
agent.printLocalData(streamId, 1) | |
stdout.write '\n' | |
while true: | |
block takeInput: | |
echo "Enter remote data (single line, no wrapping):" | |
stdout.write "> " | |
stdout.flushFile | |
let line = system.stdin.readLine | |
var | |
ufrag, passwd: string | |
remoteCandidates: seq[tuple[list: GSList, cand: NiceCandidate]] | |
var i = 0 | |
for x in line.split(' '): | |
case i: | |
of 0: | |
ufrag = x | |
of 1: | |
passwd = x | |
else: | |
var | |
foundation, ipaddr, typ: string | |
priority, port: int | |
if not scanf(x, "$*,$i,$*,$i,$*$.", foundation, priority, ipaddr, port, typ): | |
echo "Invalid candidate" | |
break takeInput | |
let ipAddress = ipaddr.parseIpAddress | |
var | |
sockAddr: Sockaddr_storage | |
sockLen: SockLen | |
toSockAddr ipAddress, port.Port, sockAddr, sockLen | |
var cand = NiceCandidate( | |
`type`: parseEnum[CandidateType](typ), | |
transport: CandidateTransport.udp, | |
priority: priority.uint32, | |
streamId: streamId, | |
componentId: 1 | |
) | |
copyMem addr cand.`addr`, addr sockAddr,sockLen | |
copyMem addr cand.foundation, foundation.cstring, min(foundation.len, CANDIDATE_MAX_FOUNDATION - 1) | |
remoteCandidates.add (list: GSList(), cand: cand) | |
remoteCandidates[^1].list.data = addr remoteCandidates[^1].cand | |
if remoteCandidates.len > 1: | |
remoteCandidates[^2].list.next = addr remoteCandidates[^1].list | |
inc i | |
if ufrag.len == 0 or passwd.len == 0: | |
echo "ufrag or passwd is empty" | |
break takeInput | |
if remoteCandidates.len == 0: | |
echo "No candidates" | |
break takeInput | |
if not agent.setRemoteCredentials(streamId.int, ufrag.cstring, passwd.cstring): | |
echo "failed to set remote credentials" | |
break takeInput | |
if agent.setRemoteCandidates(streamId.int, 1, cast[ptr pointer](addr remoteCandidates[0].list)) < 1: | |
echo "failed to set remote candidates" | |
break takeInput | |
return | |
proc cbNewSelectedPair(self: ptr Agent00; streamId: uint32; componentId: | |
uint32; lfoundation: cstring; rfoundation: cstring; data: pointer) {.cdecl.} = | |
echo "SIGNAL: selected pair ", lfoundation, ' ', rfoundation | |
proc cbStdinSendData(source: ptr IOChannel00; condition: IOCondition; data: pointer): gboolean {.cdecl.} = | |
var | |
agent = cast[ptr Agent00](data).toAgent | |
src = IOChannel(impl: source, ignoreFinalizer: true) | |
line: string | |
length, terminatorPos: uint64 | |
if src.readLine(line, length, terminatorPos) == IOStatus.normal: | |
discard agent.send(streamId, 1, line.len, line.cstring) | |
stdout.write "> " | |
stdout.flushFile | |
else: | |
discard agent.send(streamId, 1, 1, "\0") | |
gloop.quit | |
return GTrue | |
proc cbComponentStateChanged(self: ptr Agent00; streamId: uint32; componentId: uint32; | |
state: uint32; xdata: pointer) {.cdecl.} = | |
let st = ComponentState(state) | |
echo &"SIGNAL: state changed {streamId} {componentId} {st}" | |
var agent = self.toAgent | |
if st == ComponentState.connected: | |
var | |
local, remote: ptr NiceCandidate | |
loc = Candidate(impl: cast[ptr Candidate00](addr local), ignoreFinalizer: true) | |
rem = Candidate(impl: cast[ptr Candidate00](addr remote), ignoreFinalizer: true) | |
if agent.getSelectedPair(streamId.int, componentId.int, loc, rem): | |
echo &"Negotiation complete: ([{local.`addr`.addrString}]:{local.`addr`.port} [{remote.`addr`.addrString}]:{remote.`addr`.port})" | |
echo "Send lines to remote (Ctrl-D to quit):" | |
discard ioStdin.ioAddWatch(glib.PRIORITY_DEFAULT, IOCondition.`in`, cbStdinSendData, self, nil) | |
elif st == ComponentState.failed: | |
gloop.quit | |
proc cbNiceRecv(agent: ptr Agent00; streamId: uint32; componentId: uint32; len: uint32; | |
buf: cstring; userData: pointer) {.cdecl.} = | |
if len == 1 and buf[0] == '\0': | |
gloop.quit | |
var msg = newString(len) | |
for i in 0..<len: | |
msg[i] = buf[i] | |
msg.removeSuffix | |
echo msg | |
stdout.write "> " | |
stdout.flushFile | |
proc main = | |
if paramCount() > 3 or paramCount() == 0 or paramStr(1).len != 1 or paramStr(1)[0] notin {'0', '1'}: | |
quit &"Usage: {getAppFilename()} 0|1 stun_addr [stun_port]" | |
let | |
controlling = paramStr(1)[0] == '1' | |
stunAddr = if paramCount() >= 2: paramStr(2) else: "" | |
stunPort = if paramCount() >= 3: parseUInt(paramStr(3)) else: 3478 | |
if stunAddr.len != 0: | |
echo &"Using stun server '[{stunAddr}]:{stunPort}'" | |
networkingInit() | |
gloop = newMainLoop(nil, false) | |
when hostOS == "windows": | |
ioStdin = win32NewFd(system.stdin.getFileHandle) | |
else: | |
ioStdin = unixNew(system.stdin.getFileHandle) | |
var agent = newAgent(gloop.context, CompatibilityRfc5245) | |
if stunAddr.len != 0: | |
var stunIp: string | |
if stunAddr.isIpAddress: | |
stunIp = stunAddr | |
else: | |
stunIp = stunAddr.getHostByName.addrList[0] | |
agent.setProperty "stun-server", stunIp.toStringVal | |
agent.setProperty "stun-server-port", stunPort.int.toUIntVal | |
agent.setProperty "controlling-mode", toBoolVal controlling | |
discard agent.scCandidateGatheringDone(cbCandidateGatheringDone, nil, {}) | |
discard agent.scNewSelectedPair(cbNewSelectedPair, nil, {}) | |
discard agent.scComponentStateChanged(cbComponentStateChanged, nil, {}) | |
streamId = agent.addStream 1 | |
if streamId == 0: | |
quit "Failed to add stream" | |
assert agent.attachRecv(streamId, 1, gloop.context, cbNiceRecv, nil) | |
if not agent.gatherCandidates(streamId): | |
quit "Failed to start candidate gathering" | |
gloop.run | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is additional libnice wrapper code. | |
# Add following branch after `if namespace == "GObject":` block in `proc main` in gintro/tests/gen.nim | |
# if namespace == "Nice": | |
# output.write("include niceimpl\n") | |
# | |
# and `main("Nice")` after `main("cairo")` in `proc launch()` | |
# Add `nice_agent_attach_recv` that is not introspectable. | |
# https://gitlab.freedesktop.org/libnice/libnice/-/issues/37 | |
proc nice_agent_attach_recv( | |
self: ptr Agent00; | |
streamId: uint32; | |
componentId: uint32; | |
ctx: ptr glib.MainContext00; | |
`func`: AgentRecvFunc; | |
data: pointer): gboolean {. | |
importc, libprag.} | |
proc attachRecv*( | |
self: Agent; | |
streamId: int; | |
componentId: int; | |
ctx: glib.MainContext; | |
`func`: AgentRecvFunc; | |
data: pointer): bool = | |
toBool( | |
nice_agent_attach_recv( | |
cast[ptr Agent00](self.impl), | |
uint32(streamId), | |
uint32(componentId), | |
cast[ptr glib.MainContext00](ctx.impl), | |
`func`, | |
data)) | |
when defined(windows): | |
import winlean | |
else: | |
import posix | |
type | |
# See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/address.h | |
NiceAddress* {.union.} = object | |
`addr`*: SockAddr | |
ip4*: Sockaddr_in | |
ip6*: Sockaddr_in6 | |
# See https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/agent/candidate.h | |
NiceCandidate* {.pure.} = object | |
`type`*: CandidateType | |
transport*: CandidateTransport | |
`addr`*: NiceAddress | |
baseAddr*: NiceAddress | |
priority*: uint32 | |
streamId*: uint32 | |
componentId*: uint32 | |
foundation*: array[0 .. (CANDIDATE_MAX_FOUNDATION.int - 1), char] | |
username*: cstring | |
password*: cstring | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment