Started work on websocket communication: Parsing/Serialization of WebSocket packets.

This commit is contained in:
Jakob Friedl
2025-09-22 21:53:13 +02:00
parent 42cc58b30b
commit d3b37aa4a1
12 changed files with 388 additions and 61 deletions

View File

@@ -2,7 +2,7 @@
version = "0.1.0" version = "0.1.0"
author = "Jakob Friedl" author = "Jakob Friedl"
description = "Command & control framework written in Nim" description = "Conquest command & control/post-exploitation framework"
license = "BSD-3-Clause" license = "BSD-3-Clause"
srcDir = "src" srcDir = "src"
@@ -30,3 +30,4 @@ requires "ptr_math >= 0.3.0"
requires "imguin >= 1.92.2.1" requires "imguin >= 1.92.2.1"
requires "zippy >= 0.10.16" requires "zippy >= 0.10.16"
requires "mummy >= 0.4.6" requires "mummy >= 0.4.6"
requires "whisky >= 0.1.3"

View File

@@ -1,41 +1,41 @@
[Window][Sessions [Table View]] [Window][Sessions [Table View]]
Pos=10,43 Pos=10,43
Size=1533,946 Size=2016,548
Collapsed=0 Collapsed=0
DockId=0x00000003,0 DockId=0x00000003,0
[Window][Listeners] [Window][Listeners]
Pos=10,43 Pos=10,593
Size=1533,946 Size=2528,804
Collapsed=0 Collapsed=0
DockId=0x00000003,1 DockId=0x00000006,0
[Window][Eventlog] [Window][Eventlog]
Pos=1545,43 Pos=2028,43
Size=353,946 Size=510,548
Collapsed=0 Collapsed=0
DockId=0x00000004,0 DockId=0x00000004,0
[Window][Dear ImGui Demo] [Window][Dear ImGui Demo]
Pos=1545,43 Pos=2028,43
Size=353,946 Size=510,548
Collapsed=0 Collapsed=0
DockId=0x00000004,1 DockId=0x00000004,1
[Window][Dockspace] [Window][Dockspace]
Pos=0,0 Pos=0,0
Size=1908,999 Size=2548,1407
Collapsed=0 Collapsed=0
[Window][[FACEDEAD] bob@LAPTOP-02] [Window][[FACEDEAD] bob@LAPTOP-02]
Pos=956,326 Pos=10,593
Size=942,663 Size=2528,804
Collapsed=0 Collapsed=0
DockId=0x00000005,0 DockId=0x00000006,1
[Window][[C9D8E7F6] charlie@SERVER-03] [Window][[C9D8E7F6] charlie@SERVER-03]
Pos=10,434 Pos=10,593
Size=1888,555 Size=2528,804
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000006,1
@@ -45,16 +45,16 @@ Size=400,400
Collapsed=0 Collapsed=0
[Window][[G1H2I3J5] diana@WORKSTATION-04] [Window][[G1H2I3J5] diana@WORKSTATION-04]
Pos=10,434 Pos=10,593
Size=1888,555 Size=2528,804
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,1
[Window][[DEADBEEF] alice@DESKTOP-01] [Window][[DEADBEEF] alice@DESKTOP-01]
Pos=10,402 Pos=10,716
Size=1888,587 Size=2848,969
Collapsed=0 Collapsed=0
DockId=0x00000005,1 DockId=0x00000006,2
[Window][Example: Console] [Window][Example: Console]
Pos=10,572 Pos=10,572
@@ -110,8 +110,8 @@ Size=76,76
Collapsed=0 Collapsed=0
[Window][Start Listener] [Window][Start Listener]
Pos=704,387 Pos=955,591
Size=500,225 Size=637,225
Collapsed=0 Collapsed=0
[Table][0x32886A44,8] [Table][0x32886A44,8]
@@ -136,9 +136,9 @@ Column 3 Weight=0.9746
[Docking][Data] [Docking][Data]
DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF DockNode ID=0x00000009 Pos=100,200 Size=754,103 Selected=0x64D005CF
DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=1888,946 Split=Y DockSpace ID=0x85940918 Window=0x260A4489 Pos=10,43 Size=2528,1354 Split=Y
DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,389 Split=X DockNode ID=0x00000005 Parent=0x85940918 SizeRef=1888,548 Split=X
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1533,159 CentralNode=1 Selected=0x61E02D75 DockNode ID=0x00000003 Parent=0x00000005 SizeRef=1376,159 CentralNode=1 Selected=0x61E02D75
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=353,159 Selected=0x5E5F7166 DockNode ID=0x00000004 Parent=0x00000005 SizeRef=510,159 Selected=0x5E5F7166
DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,555 Selected=0x65D642C0 DockNode ID=0x00000006 Parent=0x85940918 SizeRef=1888,804 Selected=0x6BE22050

View File

@@ -1,6 +1,9 @@
import tables import whisky
import tables, strutils
import ./utils/appImGui import ./utils/appImGui
import ./views/[dockspace, sessions, listeners, eventlog, console] import ./views/[dockspace, sessions, listeners, eventlog, console]
import ../common/[types, utils]
import ./websocket
proc main() = proc main() =
var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true) var app = createApp(1024, 800, imnodes = true, title = "Conquest", docking = true)
@@ -35,6 +38,10 @@ proc main() =
let io = igGetIO() let io = igGetIO()
# Initiate WebSocket connection
let ws = newWebSocket("ws://localhost:12345")
defer: ws.close()
# main loop # main loop
while not app.handle.windowShouldClose: while not app.handle.windowShouldClose:
pollEvents() pollEvents()
@@ -44,10 +51,24 @@ proc main() =
continue continue
newFrame() newFrame()
#[
WebSocket communication with the team server
]#
# Continuously send heartbeat messages
ws.sendHeartbeat()
# Receive and parse websocket response message
let message = ws.receiveMessage().get()
case message.getMessageType()
of CLIENT_EVENT_LOG:
message.receiveEventlogItem(addr eventlog)
else: discard
# Draw/update UI components/views # Draw/update UI components/views
dockspace.draw(addr showConquest, views, addr dockTop, addr dockBottom, addr dockTopLeft, addr dockTopRight) dockspace.draw(addr showConquest, views, addr dockTop, addr dockBottom, addr dockTopLeft, addr dockTopRight)
if showSessionsTable: sessionsTable.draw(addr showSessionsTable) if showSessionsTable: sessionsTable.draw(addr showSessionsTable)
if showListeners: listenersTable.draw(addr showListeners) if showListeners: listenersTable.draw(addr showListeners, ws)
if showEventlog: eventlog.draw(addr showEventlog) if showEventlog: eventlog.draw(addr showEventlog)
# Show console windows # Show console windows
@@ -56,7 +77,7 @@ proc main() =
if console.showConsole: if console.showConsole:
# Ensure that new console windows are docked to the bottom panel by default # Ensure that new console windows are docked to the bottom panel by default
igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32) igSetNextWindowDockID(dockBottom, ImGuiCond_FirstUseEver.int32)
console.draw() console.draw(ws)
newConsoleTable[agentId] = console newConsoleTable[agentId] = console
# Update the consoles table with only those sessions that have not been closed yet # Update the consoles table with only those sessions that have not been closed yet

View File

@@ -1,3 +1,4 @@
import whisky
import strformat, strutils, times import strformat, strutils, times
import imguin/[cimgui, glfw_opengl, simple] import imguin/[cimgui, glfw_opengl, simple]
import ../utils/[appImGui, colors] import ../utils/[appImGui, colors]
@@ -113,11 +114,11 @@ proc callback(data: ptr ImGuiInputTextCallbackData): cint {.cdecl.} =
#[ #[
API to add new console item API to add new console item
]# ]#
proc addItem*(component: ConsoleComponent, itemType: LogType, data: string) = proc addItem*(component: ConsoleComponent, itemType: LogType, data: string, timestamp: int64 = now().toTime().toUnix()) =
for line in data.split("\n"): for line in data.split("\n"):
component.console.items.add(ConsoleItem( component.console.items.add(ConsoleItem(
timestamp: if itemType == LOG_OUTPUT: 0 else: now().toTime().toUnix(), timestamp: if itemType == LOG_OUTPUT: 0 else: timestamp,
itemType: itemType, itemType: itemType,
text: line text: line
)) ))
@@ -148,7 +149,7 @@ proc print(item: ConsoleItem) =
igSameLine(0.0f, 0.0f) igSameLine(0.0f, 0.0f)
igTextUnformatted(item.text.cstring, nil) igTextUnformatted(item.text.cstring, nil)
proc draw*(component: ConsoleComponent) = proc draw*(component: ConsoleComponent, ws: WebSocket) =
igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0) igBegin(fmt"[{component.agent.agentId}] {component.agent.username}@{component.agent.hostname}", addr component.showConsole, 0)
defer: igEnd() defer: igEnd()
@@ -251,22 +252,25 @@ proc draw*(component: ConsoleComponent) =
let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.int32 let inputFlags = ImGuiInputTextFlags_EnterReturnsTrue.int32 or ImGuiInputTextFlags_EscapeClearsAll.int32 or ImGuiInputTextFlags_CallbackHistory.int32 or ImGuiInputTextFlags_CallbackCompletion.int32
if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)): if igInputText("##Input", addr component.inputBuffer[0], MAX_INPUT_LENGTH, inputFlags, callback, cast[pointer](component)):
let command = $(addr component.inputBuffer[0]).cstring let command = ($(addr component.inputBuffer[0])).strip()
component.addItem(LOG_COMMAND, command) if not command.isEmptyOrWhitespace():
# For testing component.addItem(LOG_COMMAND, command)
component.addItem(LOG_ERROR, "error message")
component.addItem(LOG_SUCCESS, "success message")
component.addItem(LOG_INFO, "info message")
component.addItem(LOG_WARNING, "warning message")
component.addItem(LOG_OUTPUT, "error message\nLong output\n\tindented output\nasdasd")
# TODO: Handle command execution # For testing
# console.handleCommand(command) # component.addItem(LOG_ERROR, "error message")
# component.addItem(LOG_SUCCESS, "success message")
# component.addItem(LOG_INFO, "info message")
# component.addItem(LOG_WARNING, "warning message")
# component.addItem(LOG_OUTPUT, "error message\nLong output\n\tindented output\nasdasd")
# Add command to console history # TODO: Handle command execution
component.history.add(command) # console.handleCommand(command)
component.historyPosition = -1 ws.send("CMD:" & component.agent.agentId & ":" & command)
# Add command to console history
component.history.add(command)
component.historyPosition = -1
zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH) zeroMem(addr component.inputBuffer[0], MAX_INPUT_LENGTH)
focusInput = true focusInput = true

View File

@@ -4,7 +4,7 @@ import ../utils/[appImGui, colors]
import ../../common/types import ../../common/types
type type
EventlogComponent = ref object of RootObj EventlogComponent* = ref object of RootObj
title: string title: string
log*: ConsoleItems log*: ConsoleItems
textSelect: ptr TextSelect textSelect: ptr TextSelect
@@ -41,11 +41,11 @@ proc Eventlog*(title: string): EventlogComponent =
#[ #[
API to add new log entry API to add new log entry
]# ]#
proc addItem*(component: EventlogComponent, itemType: LogType, data: string) = proc addItem*(component: EventlogComponent, itemType: LogType, data: string, timestamp: int64 = now().toTime().toUnix()) =
for line in data.split("\n"): for line in data.split("\n"):
component.log.items.add(ConsoleItem( component.log.items.add(ConsoleItem(
timestamp: if itemType == LOG_OUTPUT: 0 else: now().toTime().toUnix(), timestamp: if itemType == LOG_OUTPUT: 0 else: timestamp,
itemType: itemType, itemType: itemType,
text: line text: line
)) ))

View File

@@ -3,11 +3,12 @@ import imguin/[cimgui, glfw_opengl, simple]
import ../utils/appImGui import ../utils/appImGui
import ../../common/[types, utils] import ../../common/[types, utils]
import ./modals/startListener import ./modals/startListener
import whisky
type type
ListenersTableComponent = ref object of RootObj ListenersTableComponent* = ref object of RootObj
title: string title: string
listeners: seq[Listener] listeners*: seq[Listener]
selection: ptr ImGuiSelectionBasicStorage selection: ptr ImGuiSelectionBasicStorage
startListenerModal: ListenerModalComponent startListenerModal: ListenerModalComponent
@@ -33,7 +34,7 @@ proc ListenersTable*(title: string): ListenersTableComponent =
result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage() result.selection = ImGuiSelectionBasicStorage_ImGuiSelectionBasicStorage()
result.startListenerModal = ListenerModal() result.startListenerModal = ListenerModal()
proc draw*(component: ListenersTableComponent, showComponent: ptr bool) = proc draw*(component: ListenersTableComponent, showComponent: ptr bool, ws: WebSocket) =
igBegin(component.title, showComponent, 0) igBegin(component.title, showComponent, 0)
defer: igEnd() defer: igEnd()
@@ -46,7 +47,7 @@ proc draw*(component: ListenersTableComponent, showComponent: ptr bool) =
let listener = component.startListenerModal.draw() let listener = component.startListenerModal.draw()
if listener != nil: if listener != nil:
# TODO: Start listener # TODO: Start listener
ws.send("Starting listener: " & listener.listenerId)
component.listeners.add(listener) component.listeners.add(listener)
#[ #[

View File

@@ -6,9 +6,9 @@ import ../utils/appImGui
import ../../common/[types, utils] import ../../common/[types, utils]
type type
SessionsTableComponent = ref object of RootObj SessionsTableComponent* = ref object of RootObj
title: string title: string
agents: seq[Agent] agents*: seq[Agent]
selection: ptr ImGuiSelectionBasicStorage selection: ptr ImGuiSelectionBasicStorage
consoles: ptr Table[string, ConsoleComponent] consoles: ptr Table[string, ConsoleComponent]

135
src/client/websocket.nim Normal file
View File

@@ -0,0 +1,135 @@
import times, tables
import ../common/[types, utils, serialize]
import views/[sessions, listeners, console, eventlog]
import whisky
#[
[ Sending Functions ]
Client -> Server
- Heartbeat
- ListenerStart
- ListenerStop
- AgentBuild
- AgentCommand
]#
proc sendHeartbeat*(ws: WebSocket) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_HEARTBEAT))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
proc sendStartListener*(ws: WebSocket, listener: Listener) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_LISTENER_START))
packer.add(string.toUUid(listener.listenerId))
packer.addDataWithLengthPrefix(string.toBytes(listener.address))
packer.add(cast[uint16](listener.port))
packer.add(cast[uint8](listener.protocol))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
proc sendStopListener*(ws: WebSocket, listenerId: string) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_LISTENER_STOP))
packer.add(string.toUuid(listenerId))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
proc sendAgentCommand*(ws: WebSocket, agentId: string, command: string) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_AGENT_COMMAND))
packer.add(string.toUuid(agentId))
packer.addDataWithLengthPrefix(string.toBytes(command))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
proc sendAgentBuild*(ws: WebSocket, listenerId: string, sleepDelay: int, sleepMask: SleepObfuscationTechnique, spoofStack: bool, modules: uint32) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_AGENT_BUILD))
packer.add(string.toUuid(listenerId))
packer.add(cast[uint32](sleepDelay))
packer.add(cast[uint8](sleepMask))
packer.add(cast[uint8](spoofStack))
packer.add(modules)
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
#[
[ Retrieval Functions ]
Server -> Client
]#
proc getMessageType*(message: Message): WsMessageAction =
var unpacker = Unpacker.init(message.data)
return cast[WsMessageAction](unpacker.getUint8())
proc receiveAgentPayload*(message: Message): seq[byte] =
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
return string.toBytes(unpacker.getDataWithLengthPrefix())
proc receiveAgentConnection*(message: Message, sessions: ptr SessionsTableComponent) =
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
let agent = Agent(
agentId: Uuid.toString(unpacker.getUint32()),
listenerId: Uuid.toString(unpacker.getUint32()),
username: unpacker.getDataWithLengthPrefix(),
hostname: unpacker.getDataWithLengthPrefix(),
domain: unpacker.getDataWithLengthPrefix(),
ip: unpacker.getDataWithLengthPrefix(),
os: unpacker.getDataWithLengthPrefix(),
process: unpacker.getDataWithLengthPrefix(),
pid: int(unpacker.getUint32()),
elevated: unpacker.getUint8() != 0,
sleep: int(unpacker.getUint32()),
tasks: @[],
firstCheckin: cast[int64](unpacker.getUint32()).fromUnix().utc(),
latestCheckin: now(),
)
sessions.agents.add(agent)
proc receiveAgentCheckin*(message: Message, sessions: ptr SessionsTableComponent)=
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
let agentId = Uuid.toString(unpacker.getUint32())
let timestamp = cast[int64](unpacker.getUint32())
# TODO: Update checkin
proc receiveConsoleItem*(message: Message, consoles: ptr Table[string, ConsoleComponent]) =
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
let
agentId = Uuid.toString(unpacker.getUint32())
logType = cast[LogType](unpacker.getUint8())
timestamp = cast[int64](unpacker.getUint32())
message = unpacker.getDataWithLengthPrefix()
consoles[][agentId].addItem(logType, message, timestamp)
proc receiveEventlogItem*(message: Message, eventlog: ptr EventlogComponent) =
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
let
logType = cast[LogType](unpacker.getUint8())
timestamp = cast[int64](unpacker.getUint32())
message = unpacker.getDataWithLengthPrefix()
eventlog[].addItem(logType, message, timestamp)

View File

@@ -81,7 +81,7 @@ type
CONFIG_PUBLIC_KEY = 4'u8 CONFIG_PUBLIC_KEY = 4'u8
CONFIG_PROFILE = 5'u8 CONFIG_PROFILE = 5'u8
LogType* = enum LogType* {.size: sizeof(uint8).} = enum
LOG_INFO = " [INFO] " LOG_INFO = " [INFO] "
LOG_ERROR = " [FAIL] " LOG_ERROR = " [FAIL] "
LOG_SUCCESS = " [DONE] " LOG_SUCCESS = " [DONE] "
@@ -193,7 +193,7 @@ type
# Listener structure # Listener structure
type type
Protocol* = enum Protocol* {.size: sizeof(uint8).} = enum
HTTP = "http" HTTP = "http"
Listener* = ref object of RootObj Listener* = ref object of RootObj
@@ -263,3 +263,84 @@ type
ConsoleItems* = ref object ConsoleItems* = ref object
items*: seq[ConsoleItem] items*: seq[ConsoleItem]
#[
Client <-> Server WebSocket communication
]#
type
WsMessageAction* = enum
# Sent by client
CLIENT_HEARTBEAT = 0'u8 # Basic checkin
CLIENT_AGENT_COMMAND = 1'u8 # Instruct TS to send queue a command for a specific agent
CLIENT_LISTENER_START = 2'u8 # Start a listener on the TS
CLIENT_LISTENER_STOP = 3'u8 # Stop a listener
CLIENT_AGENT_BUILD = 4'u8 # Generate an agent binary for a specific listener
# Sent by team server
CLIENT_AGENT_BINARY = 100'u8 # Return the agent binary to write to the operator's client machine
CLIENT_AGENT_CONNECTION = 101'u8 # Notify new agent connection
CLIENT_AGENT_CHECKIN = 102'u8 # Update agent checkin
CLIENT_CONSOLE_LOG = 103'u8 # Add entry to a agent's console
CLIENT_EVENT_LOG = 104'u8 # Add entry to the eventlog
CLIENT_CONNECTION = 200'u8 # Return team server profile
# Client -> Server
WsHeartbeat* = object
msgType* = CLIENT_HEARTBEAT
WsCommand* = object
msgType* = CLIENT_AGENT_COMMAND
agentId*: uint32
command*: seq[byte] # Command input field in the console window, prefixed with length
WsListenerStart* = object
msgType* = CLIENT_LISTENER_START
listener*: Listener
WsListenerStop* = object
msgType* = CLIENT_LISTENER_STOP
listenerId*: uint32
WsAgentBuild* = object
msgType* = CLIENT_AGENT_BUILD
listenerId*: uint32
sleepDelay*: uint32
sleepMask*: SleepObfuscationTechnique
spoofStack*: uint8
modules*: uint64
# Server -> Client
WsAgentBinary* = object
msgType* = CLIENT_AGENT_BINARY
agentPayload*: seq[byte] # Agent binary in byte-form, opens file browser to select location on the client
WsAgentConnection* = object
msgType* = CLIENT_AGENT_CONNECTION
agent*: Agent
WsAgentCheckin* = object
msgType* = CLIENT_AGENT_CHECKIN
agentId*: uint32
timestamp*: uint32
WsConsoleLog* = object
msgType* = CLIENT_CONSOLE_LOG
agentId*: uint32
logType*: LogType
timestamp*: uint32
data*: seq[byte]
WsEventLog* = object
msgType* = CLIENT_EVENT_LOG
logType*: LogType
timestamp*: uint32
data*: seq[byte]
WsClientConnection* = object
msgType* = CLIENT_CONNECTION
version: uint8
profile*: seq[byte]
agents*: seq[Agent]
listeners*: seq[Listener]

View File

@@ -1,4 +1,4 @@
import prompt, terminal, argparse, parsetoml import prompt, terminal, argparse, parsetoml, times
import strutils, strformat, system, tables import strutils, strformat, system, tables
import ./[agent, listener, builder] import ./[agent, listener, builder]
@@ -6,6 +6,8 @@ import ../globals
import ../db/database import ../db/database
import ../core/logger import ../core/logger
import ../../common/[types, crypto, profile] import ../../common/[types, crypto, profile]
import ../protocol/websocket
import mummy, mummy/routers
#[ #[
Argument parsing Argument parsing
@@ -137,6 +139,33 @@ proc init*(T: type Conquest, profile: Profile): Conquest =
cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file") cq.dbPath = CONQUEST_ROOT & "/" & profile.getString("database-file")
return cq return cq
#[
WebSocket
]#
proc upgradeHandler(request: Request) =
let ws = request.upgradeToWebSocket()
# Send client connection message
ws.sendEventlogItem(LOG_SUCCESS_SHORT, now().toTime().toUnix(), "CQ-V1")
proc websocketHandler(ws: WebSocket, event: WebSocketEvent, message: Message) =
case event:
of OpenEvent:
discard
of MessageEvent:
ws.sendHeartbeat()
of ErrorEvent:
discard
of CloseEvent:
discard
proc serve(server: Server) {.thread.} =
try:
server.serve(Port(12345))
except Exception:
discard
proc startServer*(profilePath: string) = proc startServer*(profilePath: string) =
# Ensure that the conquest root directory was passed as a compile-time define # Ensure that the conquest root directory was passed as a compile-time define
@@ -167,6 +196,13 @@ proc startServer*(profilePath: string) =
cq.restartListeners() cq.restartListeners()
cq.addMultiple(cq.dbGetAllAgents()) cq.addMultiple(cq.dbGetAllAgents())
var router: Router
router.get("/*", upgradeHandler)
let server = newServer(router, websocketHandler)
var thread: Thread[Server]
createThread(thread, serve, server)
# Main loop # Main loop
while true: while true:
cq.prompt.setIndicator("[conquest]> ") cq.prompt.setIndicator("[conquest]> ")

View File

@@ -1,5 +1,6 @@
# Compiler flags # Compiler flags
-d:server -d:server
--threads:on --threads:on
--mm:orc
-d:httpxServerName="" -d:httpxServerName=""
-o:"bin/server" -o:"bin/server"

View File

@@ -0,0 +1,47 @@
import times, tables
import ../../common/[types, utils, serialize]
import mummy
#[
[ Sending functions ]
Server -> Client
]#
proc sendHeartbeat*(ws: WebSocket) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_HEARTBEAT))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
proc sendEventlogItem*(ws: WebSocket, logType: LogType, timestamp: int64, message: string) =
var packer = Packer.init()
packer.add(cast[uint8](CLIENT_EVENT_LOG))
packer.add(cast[uint8](logType))
packer.add(cast[uint32](timestamp))
packer.addDataWithLengthPrefix(string.toBytes(message))
let data = packer.pack()
ws.send(Bytes.toString(data), BinaryMessage)
#[
[ Retrieval functions ]
Client -> Server
]#
proc getMessageType*(message: Message): WsMessageAction =
var unpacker = Unpacker.init(message.data)
return cast[WsMessageAction](unpacker.getUint8())
proc receiveStartListener*(message: Message): Listener =
var unpacker = Unpacker.init(message.data)
discard unpacker.getUint8()
return Listener(
server: nil,
listenerId: Uuid.toString(unpacker.getUint32()),
address: unpacker.getDataWithLengthPrefix(),
port: int(unpacker.getUint16()),
protocol: cast[Protocol](unpacker.getUint8())
)