syndicate-genode/src/syndicate_service.nim

317 lines
10 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: AGPL-3.0-or-later.txt
import std/[asyncdispatch, deques, options, sets, streams, tables, xmlparser, xmltree]
from std/sequtils import toSeq
import genode, genode/[entrypoints, parents, roms, servers, signals]
import preserves, preserves/xmlhooks
import syndicate, syndicate/[actors, relays]
import ./parent_facets
type
SessionLabel = seq[string]
Report {.preservesRecord: "report".} = object
label: SessionLabel
content: Assertion
SessionObj = object of RootObj
label: SessionLabel
dsCap: RamDataspaceCapability
streams: DataspaceStreamFactory
facet: Facet
dsRef: Ref
ReportSessionCapability {.
importcpp: "Genode::Capability<Report::Session>".} = object
ReportSessionComponent = Constructible[ReportSessionComponentBase]
ReportSessionComponentBase {.
importcpp: "Report::Session_component",
header: "report_service.cpp".} = object
ReportSession = ref ReportSessionObj
ReportSessionPtr = ptr ReportSessionObj
ReportSessionObj = object of SessionObj
component: ReportSessionComponent
reportHandle: Handle
RomSessionCapability {.
importcpp: "Genode::Rom_session_capability",
header: "<rom_session/capability.h>".} = object
RomSessionComponent = Constructible[RomSessionComponentBase]
RomSessionComponentBase {.
importcpp: "Rom::Session_component",
header: "rom_service.cpp".} = object
RomSession = ref RomSessionObj
RomSessionPtr = ptr RomSessionObj
RomSessionObj = object of SessionObj
component: RomSessionComponent
signalContext: SignalContextCapability
TerminalSessionCapability {.
importcpp: "Genode::Capability<Terminal::Session>".} = object
TerminalSessionComponent = Constructible[TerminalSessionComponentBase]
TerminalSessionComponentBase {.
importcpp: "Terminal::Session_component",
header: "terminal_service.cpp".} = object
TerminalSession = ref TerminalSessionObj
TerminalSessionPtr = ptr TerminalSessionObj
TerminalSessionObj = object of SessionObj
component: TerminalSessionComponent
relay: Relay
receiveBuffer: BufferedDecoder
pendingPackets: Deque[Packet]
readAvailSignal: SignalContextCapability
SessionComponent =
ReportSessionComponent |
RomSessionComponent |
TerminalSessionComponent
Session =
ReportSession |
RomSession |
TerminalSession
proc construct(
cpp: ReportSessionComponent;
state: pointer;
ds: RamDataspaceCapability) {.importcpp.}
proc construct(
cpp: RomSessionComponent;
state: pointer;
ds: RamDataspaceCapability) {.importcpp.}
proc construct(
cpp: TerminalSessionComponent;
state: pointer;
ds: RamDataspaceCapability) {.importcpp.}
proc dissolve(ep: Entrypoint; s: SessionComponent) {.importcpp: "#.dissolve(*#)".}
proc dissolve(env: GenodeEnv; s: Session) =
close(s.streams)
dissolve(env.ep, s.component)
freeDataspace(env.pd, s.dsCap)
destruct(s.component)
GC_unref s
proc dissolve(env: GenodeEnv; turn: var Turn; session: ReportSession) =
retract(turn, session.reportHandle)
dissolve(env, session)
proc dissolve(env: GenodeEnv; turn: var Turn; session: RomSession) =
dissolve(env, session)
proc dissolve(env: GenodeEnv; turn: var Turn; session: TerminalSession) =
stop(turn, session.facet)
dissolve(env, session)
proc manage(ep: Entrypoint; s: ReportSession): ReportSessionCapability =
proc manage(ep: Entrypoint; cpp: ReportSessionComponent
): ReportSessionCapability {.importcpp: "#.manage(*#)".}
result = manage(ep, s.component)
GC_ref s
proc manage(ep: Entrypoint; s: RomSession): RomSessionCapability =
proc manage(ep: Entrypoint; cpp: RomSessionComponent
): RomSessionCapability {.importcpp: "#.manage(*#)".}
result = manage(ep, s.component)
GC_ref s
proc manage(ep: Entrypoint; s: TerminalSession): TerminalSessionCapability =
proc manage(ep: Entrypoint; cpp: TerminalSessionComponent
): TerminalSessionCapability {.importcpp: "#.manage(*#)".}
result = manage(ep, s.component)
GC_ref s
proc initSession(
ses: var SessionObj;
env: GenodeEnv;
label: SessionLabel;
dsCap: RamDataspaceCapability;
facet: Facet;
dsRef: Ref) =
ses.label = label
ses.dsCap = dsCap
ses.streams = newDataspaceStreamFactory(env.rm, ses.dsCap)
ses.facet = facet
ses.dsRef = dsRef
proc newReportSession(
env: GenodeEnv;
ds: Ref; turn: var Turn;
label: SessionLabel; size: int
): ReportSession =
var dsCap = allocDataspace(env.pd, size)
new result
initSession(result[], env, label, dsCap, turn.facet, ds)
construct(result.component, addr(result[]), dsCap)
proc newRomSession(
env: GenodeEnv;
ds: Ref; turn: var Turn;
label: SessionLabel; size: int
): RomSession =
var dsCap = allocDataspace(env.pd, size)
new result
initSession(result[], env, label, dsCap, turn.facet, ds)
construct(result.component, addr(result[]), dsCap)
proc newTerminalSession(
env: GenodeEnv;
ds: Ref; turn: var Turn;
label: SessionLabel; size: int
): TerminalSession =
var dsCap = allocDataspace(env.pd, size)
result = TerminalSession(receiveBuffer: newBufferedDecoder())
initSession(result[], env, label, dsCap, turn.facet, ds)
construct(result.component, addr(result[]), dsCap)
let session = result
proc queuePacket(pkt: sink Packet): Future[void] {.async.} =
session.pendingPackets.addLast(pkt)
session.readAvailSignal.submit()
# notify the client of the pending packet
var opts = RelayActorOptions(
packetWriter: queuePacket,
initialRef: session.dsRef,
initialOid: 0.Oid.some)
run(session.facet) do (turn: var Turn):
asyncCheck spawnRelay($session.label, turn, opts) do (turn: var Turn; relay: Relay):
session.facet = turn.facet
session.relay = relay
type SessionCapability =
ReportSessionCapability |
RomSessionCapability |
TerminalSessionCapability
proc deliverSession*(parent: Parent; id: ServerId; cap: SessionCapability) {.
importcpp: "#->deliver_session_cap(Genode::Parent::Server::Id{#}, #)".}
proc splitLabel(joined: string): SessionLabel =
joined.labelElements.toSeq
proc syndicate_report_submit(state: pointer; length: csize_t) {.exportc.} =
assert not state.isNil
let session = cast[ReportSessionPtr](state)
run(session.facet) do (turn: var Turn):
var stream = newStream(session.streams, int length)
try:
replace(turn, session.dsRef, session.reportHandle, Report(
label: session.label, content: parseXml(stream).toPreserve(Ref)))
except:
var raw = newSeq[byte](stream.size)
copyMem(addr raw[0], session.streams.data, raw.len)
replace(turn, session.dsRef, session.reportHandle, Report(
label: session.label, content: raw.toPreserve(Ref)))
close(stream)
scheduleCallbacks()
proc syndicate_rom_update(state: pointer) {.exportc.} =
assert not state.isNil
let session = cast[RomSessionPtr](state)
run(session.facet) do (turn: var Turn):
var stream = newStream(session.streams)
write(stream, "syndicate_rom_update not implemented, sorry")
close(stream)
scheduleCallbacks()
proc syndicate_rom_sigh(state: pointer; cap: SignalContextCapability) {.exportc.} =
assert not state.isNil
let session = cast[RomSessionPtr](state)
session.signalContext = cap
proc syndicate_terminal_read_avail_sigh(
state: pointer; cap: SignalContextCapability) {.exportc.} =
let session = cast[TerminalSessionPtr](state)
session.readAvailSignal = cap
if session.pendingPackets.len > 0:
session.readAvailSignal.submit()
proc syndicate_terminal_avail(state: pointer): bool {.exportc.} =
let sesssion = cast[TerminalSessionPtr](state)
sesssion.pendingPackets.len > 0
proc syndicate_terminal_write(state: pointer; length: csize_t): csize_t {.exportc.} =
let sesssion = cast[TerminalSessionPtr](state)
var stream = sesssion.streams.newStream(int length)
if sesssion.pendingPackets.len > 0:
write(stream, sesssion.pendingPackets.popFirst().toPreserve(WireRef))
result = stream.getPosition.csize_t
close(stream)
if sesssion.pendingPackets.len > 0:
sesssion.readAvailSignal.submit()
scheduleCallbacks()
proc syndicate_terminal_read(state: pointer; length: csize_t): csize_t {.exportc.} =
let
session = cast[TerminalSessionPtr](state)
size = min(int length, session.streams.size)
session.receiveBuffer.feed(session.streams.data, size)
var (success, pr) = decode(session.receiveBuffer, WireRef)
if success:
dispatch(session.relay, pr)
scheduleCallbacks()
length
componentConstructHook = proc(env: GenodeEnv) =
bootDataspace("main") do (ds: Ref; turn: var Turn):
during(turn, ds, grab()) do (a: Assertion):
echo "publish: ", a
do:
echo "retract: ", a
# newConfigFacet(env, ds, turn)
newSessionRequestsFacet(env, ds, turn)
var
reports = initTable[ServerId, ReportSession]()
roms = initTable[ServerId, RomSession]()
terminals = initTable[ServerId, TerminalSession]()
onPublish(turn, ds, CreateRequest ? { 0: grab(), 1: ?Args }) do (
attrs: CreateAttrs, args: Assertion):
var
label = splitLabel(attrs.label)
id = ServerId attrs.id
case attrs.service
of "Report":
var
size = getOrDefault(args, "buffer_size", 0x1000)
session = newReportSession(env, ds, turn, label, size)
deliverSession(env.parent, id, manage(env.ep, session))
reports[id] = session
of "Rom":
const size = 0x1000
var session = newRomSession(env, ds, turn, label, size)
deliverSession(env.parent, id, manage(env.ep, session))
roms[id] = session
of "Terminal":
var
size = getOrDefault(args, "buffer_size", 0x1000)
session = newTerminalSession(env, ds, turn, label, size)
deliverSession(env.parent, id, manage(env.ep, session))
terminals[id] = session
else:
env.parent.sessionResponseDeny(id)
onPublish(turn, ds, ?CloseRequest) do (id: ServerId):
var
report: ReportSession
terminal: TerminalSession
if pop(reports, id, report):
dissolve(env, turn, report)
elif pop(terminals, id, terminal):
dissolve(env, turn, terminal)
sessionResponseClose(env.parent, id)
announce(env.parent, "Report")
announce(env.parent, "ROM")
announce(env.parent, "Terminal")
scheduleCallbacks()