This commit is contained in:
Emery Hemingway 2023-04-30 10:19:57 +01:00
commit 711a7032c0
12 changed files with 757 additions and 0 deletions

2
.envrc Normal file
View File

@ -0,0 +1,2 @@
# source_env ..
use flake sigil#checks.x86_64-linux.nova-nim.nodes.machine.config.genode.init.children.test_nim.package

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/.direnv

4
Tuprules.tup Normal file
View File

@ -0,0 +1,4 @@
include ../syndicate-nim/depends.tup
NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src
NIM_FLAGS += --path:$(TUP_CWD)/../genode-nim/src
NIM = /nix/store/zp7f2j6lsas1rfi0v2rx51c29c00xnz9-x86_64-unknown-genode-nim-wrapper-1.6.8-x86_64-unknown-genode/bin/nim

3
src/Tupfile Normal file
View File

@ -0,0 +1,3 @@
include_rules
: foreach ../*.prs |> !preserves_schema_nim |> {schema}
: syndicate_service.nim | {schema} |> !nim |>

View File

@ -0,0 +1,129 @@
# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-License-Identifier: AGPL-3.0-or-later
import std/[sets, tables]
from std/sequtils import toSeq
from std/strutils import parseHexInt, parseInt, unescape
import npeg
import preserves, preserves/xmlhooks
import ../../syndicate, ./schemas/session_requests
export Args, `$`
type
Value = Assertion
ArgsParser = object
dict: seq[(Value, Value)]
nextKey: Assertion
proc push[T](parser: var ArgsParser; v: sink T) =
add(parser.dict, (move parser.nextKey, toPreserve(v, Ref)))
const argsPeg = peg("Args", parser: ArgsParser):
# "Because friends don't let friends write parsers by hand" - Ico
Args <- Pair * *(*(Space | {','}) * Pair)
Pair <- Key * '=' * Value
Key <- +{'0'..'9', 'A'..'z', '_'}:
parser.nextKey = toSymbol($0, Ref)
for (key, _) in parser.dict:
validate(key != parser.nextKey)
# check for duplicate keys
Value <- String | Size | Hex | Natural | True | False
String <- '"' * >(*(('\\' * {'\\', '"'}) | ({'\x20'..'\x7e'} - {'\\', '"'}))) * '"':
push(parser, unescape($0))
Size <- >(+Digit) * >({'K','M','G','P'}):
var i = parseInt($1)
case $2
of "K": i = i shl 10
of "M": i = i shl 20
of "G": i = i shl 30
of "P": i = i shl 40
push(parser, i)
Hex <- "0x" * >(+Xdigit):
push(parser, parseHexInt($1))
Natural <- ('1' * +Digit) | ({'2'..'9'} * *Digit):
push(parser, parseInt($0))
True <- "true" | "on" | "yes" | "1":
push(parser, true)
False <- "false" | "off" | "no" | "0":
push(parser, false)
proc argumentsToPreserves(text: Value): (bool, Value) {.gcsafe.} =
if text.isString:
var p: ArgsParser
let match = match(argsPeg, text.string, p)
if match.ok:
result[0] = true
result[1] = initDictionary[Ref]()
result[1].dict = move p.dict
else:
raiseAssert text.string[match.matchMax..text.string.high]
type
CreateRequest* = session_requests.CreateRequest[Ref]
CloseRequest* = session_requests.CloseRequest[Ref]
CreateAttrs* = session_requests.CreateAttrs[Ref]
# TODO: schema code-generator is making these generic when they are not
when defined(genode):
import genode, genode/roms
from std/asyncdispatch import scheduleCallbacks
proc newConfigFacet*(env: GenodeEnv; ds: Ref; turn: var Turn): Facet {.discardable.} =
## Create a new `Facet` that processes the "config" ROM
## and publishes each request to `ds`.
facet(turn) do (turn: var Turn):
let facet = turn.facet
var configHandle: Handle
let configRom = env.newRomHandler("config") do (rom: RomClient):
update(rom)
run(facet) do (turn: var Turn):
var pr = rom.xml.toPreserve(Ref)
if pr.isRecord("config"):
replace(turn, ds, configHandle, pr)
scheduleCallbacks()
proc newSessionRequestsFacet*(env: GenodeEnv; ds: Ref; turn: var Turn): Facet {.discardable.} =
## Create a new `Facet` that processes the "session_requests" ROM
## and publishes each request to `ds`.
facet(turn) do (turn: var Turn):
let facet = turn.facet
var requestHandles = initTable[Preserve[Ref], Handle]()
let sessionsRom = env.newRomHandler("session_requests") do (rom: RomClient):
update(rom)
run(facet) do (turn: var Turn):
var currentRequests: HashSet[Value]
for req in rom.xml.toPreserve(Ref).fields:
var req = req
if req.isRecord "create":
# best effort conversion of the arguments ad-hoc text format
var
dict: Assertion
ok: bool
try: (ok, dict) = argumentsToPreserves(req.record[1].record[0])
except: discard
if ok: req.record[1] = move dict
else: echo "failed to parse ", req.fields[1]
incl(currentRequests, move req)
var publishedRequests = requestHandles.keys.toSeq.toHashSet
# each requests in the XML is published seperately
for req in publishedRequests:
if req notin currentRequests:
var h: Handle
discard pop(requestHandles, req, h)
retract(turn, h)
for req in currentRequests:
if req notin publishedRequests:
requestHandles[req] = publish(turn, ds, req)
scheduleCallbacks()

View File

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
// SPDX-License-Identifier: AGPL-3.0-or-later
/**
* A Report service component that propagates reports into Syndicate.
*/
#include <libc/component.h>
// TODO: until the Nim standard library is liberated
// from POSIX assume that calling any Nim proc
// requires a Libc context.
#include <report_session/report_session.h>
#include <base/rpc_server.h>
extern "C" {
void syndicate_report_submit(void *state, size_t length);
}
namespace Report {
using namespace Genode;
struct Session_component;
}
struct Report::Session_component : Genode::Rpc_object<Report::Session>
{
void *_state;
Dataspace_capability const ds_cap;
Session_component(void *state, Dataspace_capability ds)
: _state(state), ds_cap(ds)
{ }
Dataspace_capability dataspace() override { return ds_cap; }
void submit(size_t length) override
{
Libc::with_libc([this, &length] () {
syndicate_report_submit(_state, length);
});
}
void response_sigh(Genode::Signal_context_capability) override
{
Genode::warning("Report client called ", __func__);
}
size_t obtain_response() override
{
Genode::warning("Report client called ", __func__);
return 0;
}
};

View File

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
// SPDX-License-Identifier: AGPL-3.0-or-later
/**
* A ROM service component that propagates Syndicate assertions.
*/
#include <libc/component.h>
// TODO: until the Nim standard library is liberated
// from POSIX assume that calling any Nim proc
// requires a Libc context.
#include <rom_session/rom_session.h>
#include <base/rpc_server.h>
extern "C" {
void syndicate_rom_update(void *state);
void syndicate_rom_sigh(void *state, Genode::Signal_context_capability sigh);
}
namespace Rom {
using namespace Genode;
struct Session_component;
}
struct Rom::Session_component : Genode::Rpc_object<Genode::Rom_session>
{
void *_state;
Dataspace_capability const ds_cap;
Session_component(void *state, Dataspace_capability ds)
: _state(state), ds_cap(ds)
{ }
Rom_dataspace_capability dataspace() override {
return static_cap_cast<Rom_dataspace>(ds_cap); }
bool update() override { syndicate_rom_update(_state); }
void sigh(Genode::Signal_context_capability sigh) override {
syndicate_rom_sigh(_state, sigh); }
};

View File

@ -0,0 +1,76 @@
import
std/typetraits, preserves, std/tables
type
Space*[E] {.preservesRecord: "space".} = ref object
`attrs`*: SpaceAttrs[E]
LocationAttrs*[E] {.preservesDictionary.} = ref object
`width`*: BiggestInt
`height`*: BiggestInt
`xpos`*: BiggestInt
`ypos`*: BiggestInt
Args* = Table[Symbol, string]
UpgradeAttrs*[E] {.preservesDictionary.} = ref object
`id`*: BiggestInt
`ram_quota`*: BiggestInt
`cap_quota`*: BiggestInt
CreateAttrs*[E] {.preservesDictionary.} = ref object
`id`*: BiggestInt
`service`*: string
`label`*: string
SpaceAttrs*[E] {.preservesDictionary.} = ref object
`width`*: BiggestInt
`height`*: BiggestInt
UpgradeRequest*[E] {.preservesRecord: "upgrade".} = ref object
`attrs`*: UpgradeAttrs[E]
CloseAttrs*[E] {.preservesDictionary.} = ref object
`id`*: BiggestInt
CreateRequest*[E] {.preservesRecord: "create".} = ref object
`attrs`*: CreateAttrs[E]
`args`*: Args
`affinity`*: Affinity[E]
Affinity*[E] {.preservesRecord: "affinity".} = ref object
`space`*: Space[E]
`location`*: Location[E]
CloseRequest*[E] {.preservesRecord: "close".} = ref object
`attrs`*: CloseAttrs[E]
Location*[E] {.preservesRecord: "location".} = ref object
`attrs`*: LocationAttrs[E]
proc `$`*[E](x: Space[E] | LocationAttrs[E] | UpgradeAttrs[E] | CreateAttrs[E] |
SpaceAttrs[E] |
UpgradeRequest[E] |
CloseAttrs[E] |
CreateRequest[E] |
Affinity[E] |
CloseRequest[E] |
Location[E]): string =
`$`(toPreserve(x, E))
proc encode*[E](x: Space[E] | LocationAttrs[E] | UpgradeAttrs[E] |
CreateAttrs[E] |
SpaceAttrs[E] |
UpgradeRequest[E] |
CloseAttrs[E] |
CreateRequest[E] |
Affinity[E] |
CloseRequest[E] |
Location[E]): seq[byte] =
encode(toPreserve(x, E))
proc `$`*(x: Args): string =
`$`(toPreserve(x))
proc encode*(x: Args): seq[byte] =
encode(toPreserve(x))

View File

@ -0,0 +1,24 @@
; The Genode "session_requests" ROM as Preserves schema.
; See genode/repos/base/src/lib/base/session_state.cc
version 1.
; buffer contains comma-seperated key=value pairs
Args = { symbol: string ...:... }.
SpaceAttrs = { width:int height:int }.
Space = <space @attrs SpaceAttrs>.
LocationAttrs = { xpos:int ypos:int width:int height:int }.
Location = <location @attrs LocationAttrs>.
Affinity = <affinity @space Space @location Location>.
CreateAttrs = { id:int service:string label:string }.
CreateRequest = <create @attrs CreateAttrs @args Args @affinity Affinity>.
UpgradeAttrs = { id:int ram_quota:int cap_quota:int }.
UpgradeRequest = <upgrade @attrs UpgradeAttrs>.
CloseAttrs = { id:int }.
CloseRequest = <close @attrs CloseAttrs>.

View File

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
// SPDX-License-Identifier: AGPL-3.0-or-later
/**
* A Terminal service component for exchanging Syndicate packets.
*/
#include <libc/component.h>
// TODO: until the Nim standard library is liberated
// from POSIX assume that calling any Nim proc
// requires a Libc context.
#include <terminal_session/terminal_session.h>
#include <base/rpc_server.h>
extern "C" {
void syndicate_terminal_read_avail_sigh(void *state, Genode::Signal_context_capability);
bool syndicate_terminal_avail(void *state);
size_t syndicate_terminal_write(void *state, size_t length);
size_t syndicate_terminal_read(void *state, size_t length);
}
namespace Terminal {
using namespace Genode;
struct Session_component;
}
struct Terminal::Session_component
: Rpc_object<Terminal::Session, Session_component>
{
void *_state;
Dataspace_capability const _ds_cap;
Session_component(void *state, Dataspace_capability ds)
: _state(state), _ds_cap(ds)
{ }
Size size() override { return Size(0, 0); }
bool avail() override
{
bool result = false;
Libc::with_libc([this, &result] () {
result = syndicate_terminal_avail(_state);
});
return result;
}
Genode::size_t _read(Genode::size_t length)
{
size_t result = 0;
Libc::with_libc([this, length, &result] () {
result = syndicate_terminal_write(_state, length);
});
return result;
}
Genode::size_t _write(Genode::size_t length)
{
size_t result = 0;
Libc::with_libc([this, length, &result] () {
result = syndicate_terminal_read(_state, length);
});
return result;
}
Dataspace_capability _dataspace() { return _ds_cap; }
void read_avail_sigh(Signal_context_capability cap) override
{
Libc::with_libc([this, cap] () {
syndicate_terminal_read_avail_sigh(_state, cap);
});
}
void size_changed_sigh(Signal_context_capability) override { }
void connected_sigh(Signal_context_capability sigh) override
{
/*
* Immediately reflect connection-established signal to the
* client because the session is ready to use immediately after
* creation.
*/
Genode::log("send connected signal to terminal client");
Signal_transmitter(sigh).submit();
}
size_t read(void *, size_t) override { return ~0UL; }
size_t write(void const *, size_t) override { return ~0UL; }
};

316
src/syndicate_service.nim Normal file
View File

@ -0,0 +1,316 @@
# 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()

13
syndicate_genode.nimble Normal file
View File

@ -0,0 +1,13 @@
# Package
version = "20230424"
author = "Emery Hemingway"
description = "Genode Syndicate components"
license = "AGPL-3.0-or-later.txt"
srcDir = "src"
bin = @["syndicate_service"]
# Dependencies
requires "nim >= 1.6.12", "genode", "syndicate"