A new beginning
Signed-off-by: Rahix <rahix@rahix.de>
This commit is contained in:
parent
b2a12cc2dc
commit
3508870c7f
2 changed files with 304 additions and 528 deletions
479
bc-tests.lua
479
bc-tests.lua
|
|
@ -3,332 +3,185 @@ require("lunit")
|
||||||
local network = require("network")
|
local network = require("network")
|
||||||
local serialization = require("serialization")
|
local serialization = require("serialization")
|
||||||
local BaseControl = require("bc")
|
local BaseControl = require("bc")
|
||||||
local Event = BaseControl.Event
|
|
||||||
|
|
||||||
module("bc-tests", package.seeall, lunit.testcase)
|
module("bc-tests", package.seeall, lunit.testcase)
|
||||||
|
|
||||||
-- Basics {{{
|
-- Local {{{
|
||||||
function test_init()
|
function test_construct()
|
||||||
local bc = BaseControl({test_noun=true}, {test_verb=function() end})
|
local function is_a_bc(bc)
|
||||||
assert_true(bc:has_noun("test_noun"), "Local nouns were not initialized")
|
assert_true(bc.locals ~= nil)
|
||||||
assert_true(bc:has_verb("test_verb"), "Local verbs were not initialized")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function test_local_noun()
|
-- Short init
|
||||||
local bc = BaseControl({test_noun2=1234})
|
is_a_bc(BaseControl())
|
||||||
assert_equal(1234, bc:get_noun("test_noun2"), "Invalid initial value")
|
is_a_bc(BaseControl:new())
|
||||||
bc:set_noun("test_noun2", 4321)
|
is_a_bc(BaseControl:finalize())
|
||||||
assert_equal(4321, bc:get_noun("test_noun2"), "Invalid new value")
|
is_a_bc(BaseControl:finalize({}, 10))
|
||||||
|
is_a_bc(BaseControl:finalize({}))
|
||||||
|
|
||||||
|
-- Long init
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:finalize()
|
||||||
|
is_a_bc(bc)
|
||||||
|
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:finalize({}, 10)
|
||||||
|
is_a_bc(bc)
|
||||||
|
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:finalize({})
|
||||||
|
is_a_bc(bc)
|
||||||
end
|
end
|
||||||
|
|
||||||
function test_local_verb()
|
function test_register()
|
||||||
local bc = BaseControl(nil, {
|
local bc = BaseControl:new()
|
||||||
test_verb2 = function(_, _, a, b)
|
bc:register("register1", 1234)
|
||||||
assert_equal(12, a, "Invalid parameter A")
|
bc:finalize()
|
||||||
assert_equal(34, b, "Invalid parameter B")
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
bc:call_verb("test_verb2", 12, 34)
|
-- Register after finalizing
|
||||||
end
|
local bc = BaseControl:finalize()
|
||||||
|
assert_error_match("after finalizing", function()
|
||||||
function test_unknown()
|
bc:register("register2", 4321)
|
||||||
local bc = BaseControl()
|
|
||||||
|
|
||||||
assert_false(bc:has_noun("unknown"), "has_noun failed")
|
|
||||||
assert_equal(nil, bc:get_noun("unknown"), "get_noun failed")
|
|
||||||
assert_error("Unknown noun writable", function()
|
|
||||||
bc:set_noun("unknown", 1234)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Register exisiting
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:register("register3", 123)
|
||||||
|
assert_error_match("already registered", function()
|
||||||
|
bc:register("register3", 12)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_available()
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:register("available1", 1234)
|
||||||
|
bc:register("available2v", function() end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
assert_equal(1234, bc.locals.nouns["available1"])
|
||||||
|
assert_true(bc.locals.verbs["available2v"] ~= nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_get()
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:register("get1", 1234)
|
||||||
|
bc:register("get2v", function() end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
assert_equal(1234, bc:get("get1"), "wrong value for noun")
|
||||||
|
assert_equal(nil, bc:get("get2"), "got value for non-exisiting noun")
|
||||||
|
assert_error_match("not a noun", function()
|
||||||
|
bc:get("get2v")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_set()
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
bc:register("set1", 31415)
|
||||||
|
bc:register("set2v", function() end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
assert_equal(31415, bc:get("set1"), "wrong value for noun")
|
||||||
|
bc:set("set1", 14142)
|
||||||
|
assert_equal(14142, bc:get("set1"), "wrong value for noun")
|
||||||
|
|
||||||
|
assert_error_match("not a local", function()
|
||||||
|
bc:set("set2", 12345)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_error_match("can't be cast into a noun", function()
|
||||||
|
bc:set("set2v", 12345)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_pass(function()
|
||||||
|
bc:set("set2v", function() end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert_error_match("can't be cast into a verb", function()
|
||||||
|
bc:set("set1", function() end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_has_iter_nouns()
|
||||||
|
local bc = BaseControl:new(
|
||||||
|
BaseControl.Network(31213) -- Network isolation
|
||||||
|
)
|
||||||
|
bc:register("hin1", 123)
|
||||||
|
bc:register("hin2v", function() end)
|
||||||
|
bc:register("hin3", 321)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
assert_true(bc:has_noun("hin1"), "exisiting noun not found")
|
||||||
|
assert_false(bc:has_noun("hin2"), "non-existing noun found")
|
||||||
|
assert_false(bc:has_noun("hin2v"), "verb found as noun")
|
||||||
|
assert_true(bc:has_noun("hin3"), "exisiting noun not found")
|
||||||
|
|
||||||
|
local nouns = bc:nouns()
|
||||||
|
table.sort(nouns)
|
||||||
|
assert_equal(2, #nouns, "noun-list incorrect")
|
||||||
|
assert_equal("hin1", nouns[1], "noun-list incorrect")
|
||||||
|
assert_equal("hin3", nouns[2], "noun-list incorrect")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_call_verb()
|
||||||
|
local bc = BaseControl:new()
|
||||||
|
local flag1, flag2 = 0, 0
|
||||||
|
bc:register("call1", function(a, b)
|
||||||
|
flag1 = a + b
|
||||||
|
flag2 = flag2 + 1
|
||||||
|
end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
bc:call("call1", 1, 2)
|
||||||
|
assert_equal(3, flag1, "call failed")
|
||||||
|
assert_equal(1, flag2, "call failed")
|
||||||
|
|
||||||
|
bc:call("call1", 10, 10)
|
||||||
|
assert_equal(20, flag1, "call failed")
|
||||||
|
assert_equal(2, flag2, "call failed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_has_iter_verbs()
|
||||||
|
local bc = BaseControl:new(
|
||||||
|
BaseControl.Network(31214) -- Network isolation
|
||||||
|
)
|
||||||
|
bc:register("hiv1v", function() end)
|
||||||
|
bc:register("hiv2", 123)
|
||||||
|
bc:register("hiv3v", function() end)
|
||||||
|
bc:finalize()
|
||||||
|
|
||||||
|
assert_true(bc:has_verb("hiv1v"), "exisiting verb not found")
|
||||||
|
assert_false(bc:has_verb("hiv2v"), "non-existing verb found")
|
||||||
|
assert_false(bc:has_verb("hiv2"), "noun found as verb")
|
||||||
|
assert_true(bc:has_verb("hiv3v"), "exisiting verb not found")
|
||||||
|
|
||||||
|
local verbs = bc:verbs()
|
||||||
|
table.sort(verbs)
|
||||||
|
assert_equal(2, #verbs, "verb-list incorrect")
|
||||||
|
assert_equal("hiv1v", verbs[1], "verb-list incorrect")
|
||||||
|
assert_equal("hiv3v", verbs[2], "verb-list incorrect")
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_iter_empty()
|
||||||
|
local bc = BaseControl:new(
|
||||||
|
BaseControl.Network(31211) -- Network isolation
|
||||||
|
):finalize()
|
||||||
|
|
||||||
|
assert_equal(0, #bc:nouns(), "nouns found in empty network")
|
||||||
|
assert_equal(0, #bc:verbs(), "verbs found in empty network")
|
||||||
end
|
end
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
-- Networking {{{
|
-- Network {{{
|
||||||
function test_hello()
|
function test_multinode()
|
||||||
local bc1 = BaseControl({hello1=true}, {hello1v=function() end})
|
local bc1 = BaseControl()
|
||||||
local bc2 = BaseControl({hello2=true}, {hello2v=function() end})
|
bc1:register("multinode1", 123)
|
||||||
|
bc1:register("multinode2v", function() end)
|
||||||
|
bc1:finalize()
|
||||||
|
|
||||||
assert_true(bc1:has_noun("hello1"), "Announcement did not come through")
|
local bc2 = BaseControl:finalize()
|
||||||
assert_true(bc2:has_noun("hello1"), "Announcement did not come through")
|
|
||||||
assert_true(bc1:has_noun("hello2"), "Announcement did not come through")
|
|
||||||
assert_true(bc2:has_noun("hello2"), "Announcement did not come through")
|
|
||||||
|
|
||||||
assert_true(bc1:has_verb("hello1v"), "Anverbcement did not come through")
|
assert_true(bc2:has_noun("multinode1"), "remote noun missing")
|
||||||
assert_true(bc2:has_verb("hello1v"), "Anverbcement did not come through")
|
assert_true(bc2:has_verb("multinode2v"), "remote verb missing")
|
||||||
assert_true(bc1:has_verb("hello2v"), "Anverbcement did not come through")
|
|
||||||
assert_true(bc2:has_verb("hello2v"), "Anverbcement did not come through")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_remote_noun()
|
|
||||||
local a = {n = "A", bc = BaseControl({noun1=1234}), addr = network.get_scene()}
|
|
||||||
local b = {n = "B", bc = BaseControl({noun2=4321}), addr = network.get_scene()}
|
|
||||||
local c = {n = "C", bc = BaseControl(), addr = network.get_scene()}
|
|
||||||
local abc = {a, b, c, a, c}
|
|
||||||
|
|
||||||
for _, bc in ipairs(abc) do
|
|
||||||
network.set_scene(bc.addr)
|
|
||||||
assert_equal(1234, bc.bc:get_noun("noun1"), bc.n..": noun1 failed!")
|
|
||||||
assert_equal(4321, bc.bc:get_noun("noun2"), bc.n..": noun2 failed!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_noun_nv()
|
|
||||||
local bc1 = BaseControl({noun_nv=1234})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
assert_equal(1234, bc2:get_noun("noun_nv"), "Wrong answer!")
|
|
||||||
network.set_scene(addr1)
|
|
||||||
bc1:set_noun("noun_nv", 4321)
|
|
||||||
network.set_scene(addr2)
|
|
||||||
assert_equal(4321, bc2:get_noun("noun_nv"), "Wrong answer!")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_remote_verb()
|
|
||||||
local tmp_a = 1
|
|
||||||
local tmp_b = 64
|
|
||||||
local a = {n = "A", bc = BaseControl(), addr = network.get_scene()}
|
|
||||||
local b = {n = "B", bc = BaseControl(nil, {verb1 = function(_, _, a, b)
|
|
||||||
assert_equal(tmp_a, a, "A is wrong!")
|
|
||||||
assert_equal(tmp_b, b, "B is wrong!")
|
|
||||||
end}), addr = network.get_scene()}
|
|
||||||
local c = {n = "C", bc = BaseControl(), addr = network.get_scene()}
|
|
||||||
local abc = {b, a, b, c, a, c}
|
|
||||||
|
|
||||||
for _, bc in ipairs(abc) do
|
|
||||||
network.set_scene(bc.addr)
|
|
||||||
bc.bc:call_verb("verb1", tmp_a, tmp_b)
|
|
||||||
tmp_a = tmp_a + 1
|
|
||||||
tmp_b = tmp_b + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_two_way_verb()
|
|
||||||
local bc1 = BaseControl(nil, {twoway1 = function(bc, _, verb, par)
|
|
||||||
bc:call_verb(verb, par * 2)
|
|
||||||
end})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
|
|
||||||
local result
|
|
||||||
local bc2 = BaseControl(nil, {twoway2 = function(bc, _, res)
|
|
||||||
result = res
|
|
||||||
end})
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
bc2:call_verb("twoway1", "twoway2", 1234)
|
|
||||||
assert_equal(2468, result, "Double call failed")
|
|
||||||
end
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- Listening {{{
|
|
||||||
function test_listen_attach()
|
|
||||||
local bc1 = BaseControl({listen1=4321})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
|
|
||||||
local tmp
|
|
||||||
local tmp2
|
|
||||||
|
|
||||||
local id = bc2:listen("listen1", Event.Change, function(newval)
|
|
||||||
if tmp == nil then
|
|
||||||
fail("Should not have been called")
|
|
||||||
end
|
|
||||||
assert_equal(tmp, newval, "Change not correct")
|
|
||||||
tmp2 = newval
|
|
||||||
end)
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
for _, t in ipairs{1, 2, 3, 4} do
|
|
||||||
tmp = t
|
|
||||||
bc1:set_noun("listen1", t)
|
|
||||||
assert_equal(t, tmp2, "Listener not called")
|
|
||||||
end
|
|
||||||
|
|
||||||
bc2:cancel(id)
|
|
||||||
|
|
||||||
bc1:set_noun("listen1", 1234)
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_listen_modes()
|
|
||||||
local bc1 = BaseControl({listen2=4321})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
local values = {
|
|
||||||
{e=Event.Change, p=nil, s=1234, snot=1234, s2=1235},
|
|
||||||
{e=Event.Rising, p=nil, s=1236, snot=1233, s2=1237},
|
|
||||||
{e=Event.Falling, p=nil, s=1236, snot=1237, s2=1235},
|
|
||||||
{e=Event.Equals, p=1337, s=1337, snot=1234, s2=1337},
|
|
||||||
{e=Event.Above, p=4321, s=4333, snot=1234, s2=4334},
|
|
||||||
{e=Event.Below, p=4321, s=1234, snot=4444, s2=1212},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test in ipairs(values) do
|
|
||||||
local active = false
|
|
||||||
local fired = false
|
|
||||||
network.set_scene(addr2)
|
|
||||||
local id = bc2:listen("listen2", test.e, test.p, function(new)
|
|
||||||
if not active then
|
|
||||||
fail("Should not have been called")
|
|
||||||
end
|
|
||||||
assert_equal(test.s, new, "Wrong value!")
|
|
||||||
fired = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
active = true
|
|
||||||
bc1:set_noun("listen2", test.s)
|
|
||||||
assert_true(fired, "Listener was not called")
|
|
||||||
|
|
||||||
fired = false
|
|
||||||
bc1:set_noun("listen2", test.snot)
|
|
||||||
assert_false(fired, "Listener was accidentally called")
|
|
||||||
|
|
||||||
network.set_scene(addr2)
|
|
||||||
active = false
|
|
||||||
bc2:cancel(id)
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
bc1:set_noun("listen2", test.s2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- }}}
|
|
||||||
|
|
||||||
-- Dirty Business {{{
|
|
||||||
function test_connectivity_loss_listening()
|
|
||||||
local bc1 = BaseControl({connectivity2=12345})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
local called = 0
|
|
||||||
local id = bc2:listen("connectivity2", Event.Change, nil, function()
|
|
||||||
called = called + 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
bc1:set_noun("connectivity2", 321)
|
|
||||||
assert_equal(1, called, "Listener was not called!")
|
|
||||||
|
|
||||||
network.deregister(addr2)
|
|
||||||
|
|
||||||
network.allow_blackhole = true
|
|
||||||
do
|
|
||||||
bc1:set_noun("connectivity2", 123)
|
|
||||||
assert_equal(1, called, "Listener was called?!")
|
|
||||||
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
local called2 = false
|
|
||||||
local id2 = bc2:listen("connectivity2", Event.Change, nil, function()
|
|
||||||
called2 = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
bc1:set_noun("connectivity2", 321)
|
|
||||||
assert_true(called2, "Listener was not called!")
|
|
||||||
assert_equal(1, called, "Original listener was called?!")
|
|
||||||
end
|
|
||||||
network.allow_blackhole = false
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_cleanup()
|
|
||||||
local bc1 = BaseControl({cleanup1=1234})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
assert_true(bc2:has_noun("cleanup1"), "Setup failed")
|
|
||||||
assert_equal(1234, bc2:get_noun("cleanup1"), "Setup failed")
|
|
||||||
|
|
||||||
network.set_scene(addr1)
|
|
||||||
bc1:cleanup()
|
|
||||||
|
|
||||||
network.set_scene(addr2)
|
|
||||||
assert_equal(nil, bc2:get_noun("cleanup1"), "Cleanup failed")
|
|
||||||
assert_false(bc2:has_noun("cleanup1"), "Cleanup failed")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_connectivity_loss()
|
|
||||||
local bc1 = BaseControl({connectivity=4433})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
assert_true(bc2:has_noun("connectivity"), "Setup failed")
|
|
||||||
assert_equal(4433, bc2:get_noun("connectivity"), "Setup failed")
|
|
||||||
|
|
||||||
network.deregister(addr1)
|
|
||||||
|
|
||||||
network.allow_blackhole = true
|
|
||||||
assert_true(bc2:has_noun("connectivity"), "Test error")
|
|
||||||
assert_equal(nil, bc2:get_noun("connectivity"), "Invalid response from offline node")
|
|
||||||
network.allow_blackhole = false
|
|
||||||
|
|
||||||
-- Come back online
|
|
||||||
local bc1 = BaseControl({connectivity=4432})
|
|
||||||
network.set_scene(addr2)
|
|
||||||
assert_true(bc2:has_noun("connectivity"), "Reconnect failed")
|
|
||||||
assert_equal(4432, bc2:get_noun("connectivity"), "Reconnect failed")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_isolation()
|
|
||||||
local bc_outside = BaseControl({isolation1=4321})
|
|
||||||
local bc1 = BaseControl({isolation2=1234}, nil, {
|
|
||||||
port=1234,
|
|
||||||
})
|
|
||||||
local bc2 = BaseControl(nil, nil, {
|
|
||||||
port=1234,
|
|
||||||
})
|
|
||||||
|
|
||||||
assert_equal(nil, bc2:get_noun("isolation1"), "Leak!")
|
|
||||||
assert_equal(1234, bc2:get_noun("isolation2"), "Port failure")
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_malicious_messages()
|
|
||||||
local bc1 = BaseControl({malicious=1234})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl({malicious2=4321})
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
|
|
||||||
network.send(addr1, bc1.port, serialization.serialize{
|
|
||||||
ty=7, -- LISTEN_REQUEST
|
|
||||||
noun="unknown", event=Event.Change, id="dead-beef",
|
|
||||||
})
|
|
||||||
network.send(addr1, bc1.port, serialization.serialize{
|
|
||||||
ty=8, -- LISTEN_NOTIFY
|
|
||||||
id="dead-beef", value=1234567,
|
|
||||||
})
|
|
||||||
network.send(addr1, bc1.port, serialization.serialize{
|
|
||||||
ty=9, -- LISTEN_CANCEL
|
|
||||||
noun="unknown", id="dead-beef",
|
|
||||||
})
|
|
||||||
|
|
||||||
assert_equal(1234, bc2:get_noun("malicious"))
|
|
||||||
end
|
|
||||||
|
|
||||||
function test_malicious_broadcast()
|
|
||||||
local bc1 = BaseControl({malicious3=1234})
|
|
||||||
local addr1 = network.get_scene()
|
|
||||||
local bc2 = BaseControl()
|
|
||||||
local addr2 = network.get_scene()
|
|
||||||
local bc3 = BaseControl()
|
|
||||||
local addr3 = network.get_scene()
|
|
||||||
|
|
||||||
network.broadcast(bc1.port, serialization.serialize{
|
|
||||||
ty=2, -- ANNOUNCE
|
|
||||||
nouns={"malicious3"}, verbs={},
|
|
||||||
})
|
|
||||||
|
|
||||||
-- bc1 can't be fooled, as it owns this noun
|
|
||||||
network.set_scene(addr1)
|
|
||||||
assert_equal(1234, bc1:get_noun("malicious3"), "Injection successful")
|
|
||||||
|
|
||||||
-- bc2 unfortunately can be fooled and must be
|
|
||||||
network.set_scene(addr2)
|
|
||||||
assert_equal(nil, bc2:get_noun("malicious3"), "Injection failed")
|
|
||||||
end
|
end
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
|
||||||
389
bc.lua
389
bc.lua
|
|
@ -1,261 +1,184 @@
|
||||||
local component = require("component")
|
local serialization = require("serialization")
|
||||||
local event = require("event")
|
|
||||||
local serializer = require("serialization")
|
|
||||||
local uuid = require("uuid")
|
local uuid = require("uuid")
|
||||||
|
|
||||||
local BC_VERSION = {0, 1}
|
local Version = {0, 1}
|
||||||
local BC_PORT = 0xBC00 | (BC_VERSION[1] << 4) | BC_VERSION[2]
|
|
||||||
|
|
||||||
local Message = {
|
local Message = {
|
||||||
Hello = 0x48454c4f,
|
Hello = 0x48454c4f,
|
||||||
Register = 0x00524547,
|
Register = 0x52454749,
|
||||||
Disband = 0x44524547,
|
Deregister = 0x44524547,
|
||||||
NounRequest = 0x4e524551,
|
NounRequest = 0x4e524551,
|
||||||
NounResponse = 0x4e524553,
|
NounResponse = 0x4e524553,
|
||||||
VerbRequest = 0x56524551,
|
VerbRequest = 0x56524551,
|
||||||
|
VerbResponse = 0x56524553,
|
||||||
ListenRequest = 0x4c524551,
|
ListenRequest = 0x4c524551,
|
||||||
ListenNotify = 0x4c4e4f54,
|
ListenNotify = 0x4c4e4f54,
|
||||||
ListenCancel = 0x4c535450,
|
ListenCancel = 0x4c535450,
|
||||||
}
|
}
|
||||||
|
|
||||||
local Event = {
|
local function query_param(self, v)
|
||||||
Change = 1,
|
return {ty=self.ty, v=v}
|
||||||
Rising = 2,
|
end
|
||||||
Falling = 3,
|
local Query = {
|
||||||
Equals = 4,
|
Change = {ty=0x434847},
|
||||||
Above = 5,
|
Rising = {ty=0x524953},
|
||||||
Below = 6,
|
Falling = {ty=0x46414c},
|
||||||
|
Equals = setmetatable({ty=0x455155}, {__call=query_param}),
|
||||||
|
Above = setmetatable({ty=0x414256}, {__call=query_param}),
|
||||||
|
Below = setmetatable({ty=0x424c4f}, {__call=query_param}),
|
||||||
}
|
}
|
||||||
|
|
||||||
local bc = {
|
-- Network ---------------------------------------------------------------- {{{
|
||||||
_version = BC_VERSION,
|
local Network = {
|
||||||
_default_port = BC_PORT,
|
default_port = 0xBC00 | (Version[1] << 4) | Version[2],
|
||||||
Event = Event,
|
|
||||||
Message = Message,
|
|
||||||
}
|
}
|
||||||
bc.__index = bc
|
|
||||||
|
|
||||||
-- Helpers
|
function Network:new(modem, port)
|
||||||
local function send_msg(self, remote, msg_table)
|
if type(modem) == "number" then
|
||||||
self.modem.send(remote, self.port, serializer.serialize(msg_table))
|
port = modem
|
||||||
|
modem = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function broadcast_msg(self, msg_table)
|
|
||||||
self.modem.broadcast(self.port, serializer.serialize(msg_table))
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:has_noun(noun)
|
|
||||||
return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:set_noun(noun, value)
|
|
||||||
local last_value = self.local_nouns[noun]
|
|
||||||
if last_value ~= nil then
|
|
||||||
self.local_nouns[noun] = value
|
|
||||||
for id, par in pairs(self.remote_listeners[noun]) do
|
|
||||||
if (par.event == Event.Change and value ~= last_value)
|
|
||||||
or (par.event == Event.Rising and value > last_value)
|
|
||||||
or (par.event == Event.Falling and value < last_value)
|
|
||||||
or (par.event == Event.Equals and value == par.evparam)
|
|
||||||
or (par.event == Event.Above and value > par.evparam)
|
|
||||||
or (par.event == Event.Below and value < par.evparam)
|
|
||||||
then
|
|
||||||
send_msg(self, par.addr, {ty=BC_MESSAGE.LISTEN_NOTIFY, id=id, value=value})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error("Noun \""..noun.."\" does not exist or is non-local!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:get_noun(noun)
|
|
||||||
if self.local_nouns[noun] ~= nil then
|
|
||||||
return self.local_nouns[noun]
|
|
||||||
elseif self.remote_nouns[noun] ~= nil then
|
|
||||||
send_msg(self, self.remote_nouns[noun], {
|
|
||||||
ty = BC_MESSAGE.NOUN_REQUEST,
|
|
||||||
noun = noun,
|
|
||||||
})
|
|
||||||
local value
|
|
||||||
event.pullFiltered(self.timeout, function(ev, ...)
|
|
||||||
if ev ~= "modem_message" then return false end
|
|
||||||
if select(2, ...) ~= self.remote_nouns[noun] then return false end
|
|
||||||
if select(3, ...) ~= self.port then return false end
|
|
||||||
local msg = serializer.unserialize(select(5, ...))
|
|
||||||
if msg.ty ~= BC_MESSAGE.NOUN_RESPONSE then return false end
|
|
||||||
if msg.noun ~= noun then return false end
|
|
||||||
|
|
||||||
value = msg.value
|
|
||||||
return true
|
|
||||||
end)
|
|
||||||
return value
|
|
||||||
else -- Not found at all
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:listen(noun, event, evparam, callback)
|
|
||||||
-- You can leave out evparam
|
|
||||||
if type(evparam) == "function" then
|
|
||||||
callback = evparam
|
|
||||||
evparam = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local remote_addr = self.remote_nouns[noun]
|
|
||||||
if remote_addr == nil then
|
|
||||||
error("Noun \""..noun.."\" is not listenable!")
|
|
||||||
else
|
|
||||||
local id = uuid.next()
|
|
||||||
self.local_listeners[id] = {addr=remote_addr, callback=callback}
|
|
||||||
send_msg(self, remote_addr, {
|
|
||||||
ty=BC_MESSAGE.LISTEN_REQUEST,
|
|
||||||
noun=noun,
|
|
||||||
event=event,
|
|
||||||
evparam=evparam,
|
|
||||||
id=id,
|
|
||||||
})
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:cancel(id)
|
|
||||||
local l = self.local_listeners[id]
|
|
||||||
send_msg(self, l.addr, {ty=BC_MESSAGE.LISTEN_CANCEL, noun=l.noun, id=id})
|
|
||||||
self.local_listeners[id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:has_verb(verb)
|
|
||||||
return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:call_verb(verb, ...)
|
|
||||||
if self.local_verbs[verb] ~= nil then
|
|
||||||
self.local_verbs[verb](self, nil, ...)
|
|
||||||
elseif self.remote_verbs[verb] ~= nil then
|
|
||||||
send_msg(self, self.remote_verbs[verb], {
|
|
||||||
ty=BC_MESSAGE.VERB_REQUEST, verb=verb, param={...},
|
|
||||||
})
|
|
||||||
else
|
|
||||||
error("Verb \""..verb.."\" does not exist!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:cleanup()
|
|
||||||
event.ignore("modem_message", self._modem_listener)
|
|
||||||
local nouns, verbs = self:locals()
|
|
||||||
broadcast_msg(self, {
|
|
||||||
ty=BC_MESSAGE.DISBAND,
|
|
||||||
nouns=nouns,
|
|
||||||
verbs=verbs,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function bc:locals()
|
|
||||||
local nouns, verbs = {}, {}
|
|
||||||
for noun in pairs(self.local_nouns) do table.insert(nouns, noun) end
|
|
||||||
for verb in pairs(self.local_verbs) do table.insert(verbs, verb) end
|
|
||||||
return nouns, verbs
|
|
||||||
end
|
|
||||||
|
|
||||||
local function new(_, local_nouns, local_verbs, overrides)
|
|
||||||
local self = {
|
local self = {
|
||||||
local_nouns=local_nouns or {},
|
modem = modem or require("component").modem,
|
||||||
local_verbs=local_verbs or {},
|
port = port or Network.default_port,
|
||||||
remote_nouns={},
|
|
||||||
remote_verbs={},
|
|
||||||
|
|
||||||
local_listeners={},
|
|
||||||
remote_listeners={},
|
|
||||||
|
|
||||||
port=BC_PORT,
|
|
||||||
modem=component.modem,
|
|
||||||
timeout=5,
|
|
||||||
}
|
}
|
||||||
if overrides ~= nil then
|
setmetatable(self, {__index=Network})
|
||||||
for name, value in pairs(overrides) do
|
|
||||||
self[name] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
setmetatable(self, bc)
|
|
||||||
|
|
||||||
for noun in pairs(self.local_nouns) do
|
return self
|
||||||
self.remote_listeners[noun] = {}
|
|
||||||
end
|
end
|
||||||
|
setmetatable(Network, {__call=Network.new})
|
||||||
|
|
||||||
self._modem_listener = function(_, _, remote_addr, port, _, msg)
|
function Network:start(callback)
|
||||||
if port ~= self.port then -- Ignore other ports
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local msg = serializer.unserialize(msg)
|
|
||||||
if msg.ty == BC_MESSAGE.HELLO then
|
|
||||||
local nouns, verbs = self:locals()
|
|
||||||
send_msg(self, remote_addr, {
|
|
||||||
ty=BC_MESSAGE.REGISTER,
|
|
||||||
nouns=nouns,
|
|
||||||
verbs=verbs,
|
|
||||||
})
|
|
||||||
elseif msg.ty == BC_MESSAGE.REGISTER then
|
|
||||||
for _, noun in ipairs(msg.nouns or {}) do
|
|
||||||
self.remote_nouns[noun] = remote_addr
|
|
||||||
end
|
|
||||||
for _, verb in ipairs(msg.verbs or {}) do
|
|
||||||
self.remote_verbs[verb] = remote_addr
|
|
||||||
end
|
|
||||||
elseif msg.ty == BC_MESSAGE.NOUN_REQUEST then
|
|
||||||
send_msg(self, remote_addr, {
|
|
||||||
ty=BC_MESSAGE.NOUN_RESPONSE,
|
|
||||||
noun=msg.noun,
|
|
||||||
value=self.local_nouns[msg.noun],
|
|
||||||
})
|
|
||||||
elseif msg.ty == BC_MESSAGE.VERB_REQUEST then
|
|
||||||
local callback = self.local_verbs[msg.verb]
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(self, remote_addr, table.unpack(msg.param))
|
|
||||||
end
|
|
||||||
elseif msg.ty == BC_MESSAGE.LISTEN_REQUEST then
|
|
||||||
if self.local_nouns[msg.noun] ~= nil then
|
|
||||||
self.remote_listeners[msg.noun][msg.id] = {
|
|
||||||
event=msg.event,
|
|
||||||
evparam=msg.evparam,
|
|
||||||
addr=remote_addr,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
elseif msg.ty == BC_MESSAGE.LISTEN_NOTIFY then
|
|
||||||
local listener = self.local_listeners[msg.id]
|
|
||||||
if listener ~= nil and listener.addr == remote_addr then
|
|
||||||
listener.callback(msg.value)
|
|
||||||
end
|
|
||||||
elseif msg.ty == BC_MESSAGE.LISTEN_CANCEL then
|
|
||||||
if self.remote_listeners[msg.noun] ~= nil then
|
|
||||||
self.remote_listeners[msg.noun][msg.id] = nil
|
|
||||||
end
|
|
||||||
elseif msg.ty == BC_MESSAGE.DISBAND then
|
|
||||||
for _, noun in ipairs(msg.nouns or {}) do
|
|
||||||
if self.remote_nouns[noun] == remote_addr then
|
|
||||||
self.remote_nouns[noun] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
for _, verb in ipairs(msg.verbs or {}) do
|
|
||||||
if self.remote_verbs[verb] == remote_addr then
|
|
||||||
self.remote_verbs[verb] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Setup connection and say hello
|
|
||||||
self.modem.open(self.port)
|
self.modem.open(self.port)
|
||||||
event.listen("modem_message", self._modem_listener)
|
self.listener = function(_, addr_lo, addr_remote, port, _, msg)
|
||||||
broadcast_msg(self, {ty=BC_MESSAGE.HELLO})
|
-- Filter everything we don't care about
|
||||||
local nouns, verbs = self:locals()
|
if addr_lo ~= self.modem.address then return end
|
||||||
broadcast_msg(self, {
|
if port ~= self.port then return end
|
||||||
ty=BC_MESSAGE.REGISTER,
|
callback(addr_remote, serialization.unserialize(msg))
|
||||||
nouns=nouns,
|
end
|
||||||
verbs=verbs,
|
require("event").listen("modem_message", self.listener)
|
||||||
})
|
end
|
||||||
|
|
||||||
|
function Network:send(addr, msg)
|
||||||
|
self.modem.send(addr, self.port, serialization.serialize(msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Network:broadcast(msg)
|
||||||
|
self.modem.broadcast(self.port, serialization.serialize(msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Network:stop()
|
||||||
|
require("event").ignore("modem_message", self.listener)
|
||||||
|
self.modem.close(self.port)
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- BaseControl ------------------------------------------------------------ {{{
|
||||||
|
local BaseControl = {
|
||||||
|
version = Version,
|
||||||
|
Message = Message,
|
||||||
|
Query = Query,
|
||||||
|
Network = Network,
|
||||||
|
}
|
||||||
|
|
||||||
|
function BaseControl:new(self, network)
|
||||||
|
local self = {
|
||||||
|
locals = {
|
||||||
|
nouns = {},
|
||||||
|
verbs = {},
|
||||||
|
},
|
||||||
|
remotes = {
|
||||||
|
nouns = {},
|
||||||
|
verbs = {},
|
||||||
|
},
|
||||||
|
live = false,
|
||||||
|
network = network or Network:new(),
|
||||||
|
}
|
||||||
|
setmetatable(self, {__index=BaseControl, __call=BaseControl.new})
|
||||||
|
|
||||||
|
self.network:start(function() error("net unimplemented") end)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
return setmetatable(bc, {__call=new, __index={new=new}})
|
function BaseControl:register(name, value)
|
||||||
|
if self.live then
|
||||||
|
error("can't register \""..name.."\" after finalizing")
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(value) == "function" then
|
||||||
|
if self.locals.verbs[name] ~= nil then
|
||||||
|
error("\""..name.."\" already registered")
|
||||||
|
else
|
||||||
|
self.locals.verbs[name] = value
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if self.locals.nouns[name] ~= nil then
|
||||||
|
error("\""..name.."\" already registered")
|
||||||
|
else
|
||||||
|
self.locals.nouns[name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:finalize(waits, timeout)
|
||||||
|
local self = self
|
||||||
|
if self == BaseControl then
|
||||||
|
-- Called as a constructor
|
||||||
|
self = BaseControl:new()
|
||||||
|
end
|
||||||
|
|
||||||
|
self.live = true
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nouns {{{
|
||||||
|
function BaseControl:has_noun(noun)
|
||||||
|
return self.locals.nouns[noun] ~= nil or self.remotes.nouns[noun] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:nouns(local_only)
|
||||||
|
local nouns = {}
|
||||||
|
for noun in pairs(self.locals.nouns) do
|
||||||
|
table.insert(nouns, noun)
|
||||||
|
end
|
||||||
|
-- TODO: Remote
|
||||||
|
return nouns
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:get(noun, timeout)
|
||||||
|
if self.locals.nouns[noun] ~= nil then
|
||||||
|
return self.locals.nouns[noun]
|
||||||
|
elseif self.locals.verbs[noun] ~= nil then
|
||||||
|
error("\""..noun.."\" is not a noun")
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:set(name, value)
|
||||||
|
error("set unimplemented")
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
-- Verbs {{{
|
||||||
|
function BaseControl:has_verb(verb)
|
||||||
|
return self.locals.verbs[verb] ~= nil or self.remotes.verbs[verb] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:verbs(local_only)
|
||||||
|
local verbs = {}
|
||||||
|
for verb in pairs(self.locals.verbs) do
|
||||||
|
table.insert(verbs, verb)
|
||||||
|
end
|
||||||
|
-- TODO: Remote
|
||||||
|
return verbs
|
||||||
|
end
|
||||||
|
|
||||||
|
function BaseControl:call(verb, ...)
|
||||||
|
error("call unimplemented")
|
||||||
|
end
|
||||||
|
-- }}}
|
||||||
|
-- }}}
|
||||||
|
|
||||||
|
return setmetatable(BaseControl, {__call=BaseControl.new})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue