# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[asyncdispatch, logging, parseopt, strutils, tables] from std/sequtils import toSeq from std/times import inMilliseconds import preserves, syndicate, syndicate/actors import toxcore import ./protocol # import ./logging addHandler(newConsoleLogger(useStderr = true)) # register global logger to stderr proc logging_callback( core: Core; level: Log_Level; file: cstring; line: uint32; `func`: cstring; message: cstring; user_data: pointer) {.exportc, cdecl.} = let lvl = case level of TOX_LOG_LEVEL_TRACE: lvlDebug of TOX_LOG_LEVEL_DEBUG: lvlDebug of TOX_LOG_LEVEL_INFO: lvlInfo of TOX_LOG_LEVEL_WARNING: lvlWarn of TOX_LOG_LEVEL_ERROR: lvlError if lvl != lvlNone: log(lvl, `func`, ": ", message) type Entity = ref object of RootObj facet: Facet ds: Ref FriendHandles = object name, statusMessage, lastOnline, typing: Handle FriendEntity {.final.} = ref object of Entity handles: FriendHandles CoreHandles = object address, name, statusMessage, connectionStatus: Handle CoreEntity {.final.} = ref object of Entity core: Tox statusCounts: array[3, int] handles: CoreHandles friendRequests: Table[toxcore.PublicKey, Handle] friendEntities: seq[FriendEntity] proc init(e: Entity; turn: var Turn; parent: Ref): Handle = assert e.facet.isNil e.facet = turn.facet e.ds = newRef(turn, parent.target) proc initCore(entity: CoreEntity; turn: var Turn; parentRef: Ref) = assert entity.core.isNil block: # Tox initialization var proxy_host: cstring entity.core = newTox do (opts: toxcore.Options): opts.log_callback = logging_callback debug "parsing command-line options…" proc parseBoolParam(key, val: string): bool = if val == "": result = true else: try: result = parsebool(val) except: quit("failed to parse " & key & " as boolean: " & val) proc parsePortParam(key, val: string): uint16 = try: result = uint16 parsebool(val) except: quit("failed to parse " & key & " as port: " & val) for kind, key, val in getopt(): case kind of cmdLongOption: case key of "ipv6": opts.ipv6_enabled = parseBoolParam(key, val) of "udp": opts.udp_enabled = parseBoolParam(key, val) of "local-discovery": opts.local_discovery_enabled = parseBoolParam(key, val) of "proxy": case val of "none": opts.proxy_type = TOX_PROXY_TYPE_NONE of "http": opts.proxy_type = TOX_PROXY_TYPE_HTTP of "socks5": opts.proxy_type = TOX_PROXY_TYPE_SOCKS5 else: quit("unhandled proxy type: " & val) of "proxy-host": proxy_host = val opts.proxy_host = proxy_host of "proxy-port": opts.proxy_port = parsePortParam(key, val) of "start-port": opts.start_port = parsePortParam(key, val) of "end-port": opts.end_port = parsePortParam(key, val) of "tcp-port": opts.tcp_port = parsePortParam(key, val) of "hole-punching": opts.hole_punching_enabled = parseBoolParam(key, val) of "save-file": discard else: quit("unhandled command-line parameter: " & key) of cmdShortOption, cmdArgument: quit("unhandled command-line parameter: " & key) of cmdEnd: discard block: # Syndicate entity initialization discard init(entity, turn, parentRef) discard publish(turn, parentRef, ToxDataspace[Ref]( publicKey: entity.core.publicKey.bytes.toSeq, entity: entity.ds.embed)) discard publish(turn, entity.ds, CoreVersion( major: int version_major(), minor: int version_minor(), patch: int version_patch())) entity.handles.address = publish(turn, entity.ds, protocol.Address(text: $entity.core.address)) entity.handles.name = publish(turn, entity.ds, Name(name: entity.core.name)) entity.handles.statusMessage = publish(turn, entity.ds, StatusMessage(msg: entity.core.statusMessage)) block: # Friends initialization var friendNums = entity.core.friends entity.friendEntities.setLen(friendNums.len) for fn in friendNums: var fe = new FriendEntity discard init(fe, turn, entity.ds) discard publish(turn, entity.ds, FriendDataspace[Ref]( publicKey: entity.core.publicKey(fn).bytes.toSeq, entity: fe.ds.embed)) if fn.int > entity.friendEntities.len: entity.friendEntities.setLen(fn.int.succ) entity.friendEntities[int fn] = fe var alive: bool setControlCHook do: if not alive: quit() alive = false proc run(entity: CoreEntity) = alive = true bootDataspace("main") do (ds: Ref; turn: var Turn): connectStdio(ds, turn) initCore(entity, turn, ds) onPublish(turn, ds, ?BootstrapNode) do (key: string; host: string; port: int): info "Bootstrapping from ", key, "@", host, ":", port try: entity.core.bootstrap(host, key.toPublicKey, uint16 port) except ToxError as e: error "failed to bootstrap: ", e.msg poll() while alive: iterate entity.core poll(entity.core.iterationInterval.inMilliseconds.int) run(new CoreEntity)