Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion src/devicecode/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,51 @@ local function move_to_front(list, wanted)
return out
end

local function shallow_copy(t)
local out = {}
if type(t) == 'table' then
for k, v in pairs(t) do out[k] = v end
end
return out
end

local function env_non_empty(name)
local v = os.getenv(name)
if v == nil or v == '' then return nil end
return v
end

local function ui_opts_with_env_auth(opts)
if type(opts) == 'table' and (opts.auth ~= nil or opts.auth_opts ~= nil) then
return opts
end

local password = env_non_empty('DEVICECODE_UI_ADMIN_PASSWORD')
if not password then return opts end

local username = env_non_empty('DEVICECODE_UI_ADMIN_USERNAME')
or env_non_empty('DEVICECODE_UI_ADMIN_USER')
or 'admin'

local out = shallow_copy(opts)
out.auth_opts = {
users = {
[username] = {
password = password,
principal = { kind = 'user', id = username },
},
},
}
return out
end

local function service_opts_for(name, opts)
if name == 'ui' then
return ui_opts_with_env_auth(opts)
end
return opts
end

local function cleanup_child_scope(child, reason)
if not child then return end
child:cancel(reason or 'cleanup')
Expand All @@ -80,6 +125,8 @@ local function spawn_service(child, bus, name, mod, env, extra_opts)
services = extra_opts and extra_opts.services or nil,
run_http = extra_opts and extra_opts.run_http or nil,
verify_login = extra_opts and extra_opts.verify_login or nil,
auth = extra_opts and extra_opts.auth or nil,
auth_opts = extra_opts and extra_opts.auth_opts or nil,
})

error(('service returned unexpectedly: %s'):format(tostring(name)), 0)
Expand Down Expand Up @@ -222,7 +269,7 @@ function M.run(scope, params)
fail_boot(main_conn, name, 'child_scope_failed', cerr)
end

local ok_spawn, serr = spawn_service(child, bus, name, mod, env, service_opts[name])
local ok_spawn, serr = spawn_service(child, bus, name, mod, env, service_opts_for(name, service_opts[name]))
if not ok_spawn then
cleanup_child_scope(child, 'spawn_failed')
fail_boot(main_conn, name, 'spawn_failed', serr)
Expand Down Expand Up @@ -309,4 +356,8 @@ function M.run(scope, params)
end
end

M._test = {
service_opts_for = service_opts_for,
}

return M
22 changes: 21 additions & 1 deletion src/services/fabric/hal_transport.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ local op = require 'fibers.op'
local protocol = require 'services.fabric.protocol'
local resource = require 'devicecode.support.resource'
local cap_sdk = require 'services.hal.sdk.cap'
local cap_args = require 'services.hal.types.capability_args'
local dep_failure = require 'devicecode.support.dependency_failure'

local M = {}
Expand Down Expand Up @@ -264,6 +265,21 @@ local function reason_text(reason, fallback)
return reason or fallback or 'transport_open_failed'
end

local function open_opts_for_transport(transport_cfg)
local opts = transport_cfg.open_opts
if transport_cfg.class == 'uart' then
if opts == nil or getmetatable(opts) ~= cap_args.UARTOpenOpts then
local open_opts, err = cap_args.new.UARTOpenOpts(opts)
if not open_opts then
return nil, transport_open_error(transport_cfg, err or 'invalid_uart_open_opts', err)
end
return open_opts, nil
end
end

return opts or {}, nil
end

local function unwrap_open_transport_reply(transport_cfg, reply, err)
-- Backwards-compatible public helper: old callers passed (reply, err).
-- New internal callers pass (transport_cfg, reply, err) so structured failures
Expand Down Expand Up @@ -331,10 +347,14 @@ function M.open_transport_op(conn, transport_cfg, transport_session)
transport_cfg.class,
transport_cfg.id
)
local open_opts, opts_err = open_opts_for_transport(transport_cfg)
if not open_opts then
return op.always(nil, opts_err)
end

return cap:call_control_op(
transport_cfg.open_verb or 'open',
transport_cfg.open_opts
open_opts
):wrap(function (reply, err)
local session, uerr = unwrap_open_transport_reply(transport_cfg, reply, err)
if not session then
Expand Down
38 changes: 28 additions & 10 deletions src/services/fabric/session.lua
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,23 @@ local function same_peer(cur, frame)
and frame.sid == cur.peer_sid
end

local function is_self_control_frame(cur, frame)
if type(frame) ~= 'table' or type(frame.sid) ~= 'string' then return false end
if frame.sid ~= cur.local_sid then return false end
if frame.type == 'hello' or frame.type == 'hello_ack' then
return frame.node == nil or frame.node == cur.local_node
end
if frame.type == 'ping' or frame.type == 'pong' then return true end
return false
end

local function is_unexpected_peer(self, frame)
local expected = self._expected_peer
if expected == nil or expected == '' then return false end
if frame.type ~= 'hello' and frame.type ~= 'hello_ack' then return false end
return frame.node ~= expected
end

local function establish_from_peer(self, frame, at)
at = at or fibers.now()
local cur = session_snapshot(self)
Expand Down Expand Up @@ -393,21 +410,15 @@ end

local function send_hello(self)
local cur = session_snapshot(self)
must_admit_control_frame_now(
self._tx_control,
assert(protocol.hello(cur.local_sid, self._local_node, self._identity_claim, self._auth_claim)),
'session_hello_send_failed'
)
local frame = assert(protocol.hello(cur.local_sid, self._local_node, self._identity_claim, self._auth_claim))
must_admit_control_frame_now(self._tx_control, frame, 'session_hello_send_failed')
self._next_hello_at = fibers.now() + self._hello_interval
end

local function send_hello_ack(self)
local cur = session_snapshot(self)
must_admit_control_frame_now(
self._tx_control,
assert(protocol.hello_ack(cur.local_sid, self._local_node, self._identity_claim, self._auth_claim)),
'session_hello_ack_send_failed'
)
local frame = assert(protocol.hello_ack(cur.local_sid, self._local_node, self._identity_claim, self._auth_claim))
must_admit_control_frame_now(self._tx_control, frame, 'session_hello_ack_send_failed')
end

local function send_ping(self)
Expand Down Expand Up @@ -554,6 +565,12 @@ end

local function handle_session_frame(self, checked, at)
local cur = session_snapshot(self)
if is_self_control_frame(cur, checked) then
return
end
if is_unexpected_peer(self, checked) then
return
end
if (checked.type == 'hello' or checked.type == 'hello_ack')
and not protocol.proto_supported(checked.proto)
then
Expand Down Expand Up @@ -679,6 +696,7 @@ function M.run(scope, params)
_transfer_tx = transfer_tx,
_session_model = session_model,
_local_node = local_node,
_expected_peer = params.peer_id,
_identity_claim = protocol.normalise_reserved_claim(params.identity_claim),
_auth_claim = protocol.normalise_reserved_claim(params.auth_claim),
_auth_state = 'unauthenticated',
Expand Down
21 changes: 9 additions & 12 deletions src/services/hal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,20 @@ end

local function device_source_id(device)
local meta = device.meta or {}
local candidates = {
meta.source_id,
meta.source,
meta.devpath,
meta.path,
meta.name,
meta.serial,
meta.uid,
}

for i = 1, #candidates do
local v = candidates[i]
local function source_token(v)
if type(v) == 'string' and v ~= '' then return path_token(v) end
if type(v) == 'number' then return path_token(v) end
end

return path_token(('%s_%s'):format(tostring(device.class), tostring(device.id)))
return source_token(meta.source_id)
or source_token(meta.source)
or source_token(meta.devpath)
or source_token(meta.path)
or source_token(meta.name)
or source_token(meta.serial)
or source_token(meta.uid)
or path_token(('%s_%s'):format(tostring(device.class), tostring(device.id)))
end

----------------------------------------------------------------------
Expand Down
53 changes: 47 additions & 6 deletions src/services/hal/managers/uart.lua
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,47 @@ local function valid_mode(mode)
or mode == '8O1'
end

local function validate_config(entries)
if type(entries) ~= 'table' then
return false, 'config must be a list'
end
local function is_sequence(t)
if type(t) ~= 'table' then
return false
end

local n = 0
for k in pairs(t) do
if type(k) ~= 'number' or k < 1 or k % 1 ~= 0 then
return false
end
n = n + 1
end

return n == #t
end

local function normalise_config(raw)
if type(raw) ~= 'table' then
return nil, 'config must be a list or table with serial_ports list'
end

if raw.serial_ports ~= nil then
for k in pairs(raw) do
if k ~= 'serial_ports' then
return nil, 'uart config only supports serial_ports'
end
end
if not is_sequence(raw.serial_ports) then
return nil, 'uart serial_ports must be a list'
end
return raw.serial_ports, nil
end

if not is_sequence(raw) then
return nil, 'uart config must be a list or table with serial_ports list'
end

return raw, nil
end

local function validate_config(entries)
for _, entry in ipairs(entries) do
if type(entry) ~= 'table' then
return false, 'each uart entry must be a table'
Expand Down Expand Up @@ -282,7 +318,12 @@ end

function M.apply_config_op(entries)
return fibers.run_scope_op(function ()
local ok, err = validate_config(entries)
local normalised, norm_err = normalise_config(entries)
if not normalised then
return false, norm_err
end

local ok, err = validate_config(normalised)
if not ok then
return false, err
end
Expand All @@ -299,7 +340,7 @@ function M.apply_config_op(entries)
local reply_ch = channel.new(1)
local admitted, admit_err = fibers.perform(cfg_ch:put_op({
generation = generation,
config = entries,
config = normalised,
reply_ch = reply_ch,
}):wrap(function ()
return true, nil
Expand Down
8 changes: 7 additions & 1 deletion src/services/ui/http/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ if not ok_http_headers then http_headers = nil end

local M = {}

local function default_encode_json(value)
local encoded, err = cjson.encode(value)
if encoded == nil then error(err or 'json_encode_failed', 0) end
return encoded
end

local function header_one(headers, name)
if not headers then return nil end
if http_headers and type(http_headers.get_one) == 'function' then
Expand Down Expand Up @@ -229,7 +235,7 @@ end

function M.run(scope, ctx, deps)
deps = deps or {}
local owner = response_mod.new(ctx, { encode = deps.encode_json })
local owner = response_mod.new(ctx, { encode = deps.encode_json or default_encode_json })

scope:finally(function (_, status, primary)
resource.terminate_checked(owner, primary or status or 'request_closed', 'HTTP response termination')
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/fabric/test_link.lua
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function tests.test_session_control_establishes_from_hello_and_sends_ack()
local ok, err = scope:spawn(function ()
local result = session.run(scope, {
link_id = 'link-session',
peer_id = 'peer-a',
peer_id = 'peer-node',
local_node = 'local-a',
local_sid = 'local-sid',
frame_rx = control_rx,
Expand Down Expand Up @@ -133,7 +133,7 @@ function tests.test_session_liveness_timeout_resets_to_hello()
local ok, err = scope:spawn(function ()
local result = session.run(scope, {
link_id = 'link-liveness',
peer_id = 'peer-a',
peer_id = 'peer-node',
local_sid = 'local-sid',
frame_rx = control_rx,
tx_control = out_tx,
Expand Down Expand Up @@ -198,7 +198,7 @@ function tests.test_session_ping_is_emitted_before_liveness_deadline()
local ok, err = scope:spawn(function ()
local result = session.run(scope, {
link_id = 'link-ping',
peer_id = 'peer-a',
peer_id = 'peer-node',
local_sid = 'local-sid',
frame_rx = control_rx,
tx_control = out_tx,
Expand Down Expand Up @@ -247,7 +247,7 @@ function tests.test_session_control_processes_ready_control_before_timer_work()
local ok, err = scope:spawn(function ()
local result = session.run(scope, {
link_id = 'link-control-before-timer',
peer_id = 'peer-a',
peer_id = 'peer-node',
local_sid = 'local-sid',
frame_rx = control_rx,
tx_control = out_tx,
Expand Down
Loading