diff --git a/bc-tests.lua b/bc-tests.lua index b0a76ee81b18..54f95b78063e 100644 --- a/bc-tests.lua +++ b/bc-tests.lua @@ -3,332 +3,185 @@ require("lunit") local network = require("network") local serialization = require("serialization") local BaseControl = require("bc") -local Event = BaseControl.Event module("bc-tests", package.seeall, lunit.testcase) --- Basics {{{ -function test_init() - local bc = BaseControl({test_noun=true}, {test_verb=function() end}) - assert_true(bc:has_noun("test_noun"), "Local nouns were not initialized") - assert_true(bc:has_verb("test_verb"), "Local verbs were not initialized") -end - -function test_local_noun() - local bc = BaseControl({test_noun2=1234}) - assert_equal(1234, bc:get_noun("test_noun2"), "Invalid initial value") - bc:set_noun("test_noun2", 4321) - assert_equal(4321, bc:get_noun("test_noun2"), "Invalid new value") -end +-- Local {{{ +function test_construct() + local function is_a_bc(bc) + assert_true(bc.locals ~= nil) + end -function test_local_verb() - local bc = BaseControl(nil, { - test_verb2 = function(_, _, a, b) - assert_equal(12, a, "Invalid parameter A") - assert_equal(34, b, "Invalid parameter B") - end, - }) + -- Short init + is_a_bc(BaseControl()) + is_a_bc(BaseControl:new()) + is_a_bc(BaseControl:finalize()) + is_a_bc(BaseControl:finalize({}, 10)) + is_a_bc(BaseControl:finalize({})) - bc:call_verb("test_verb2", 12, 34) -end + -- Long init + local bc = BaseControl:new() + bc:finalize() + is_a_bc(bc) -function test_unknown() - local bc = BaseControl() + local bc = BaseControl:new() + bc:finalize({}, 10) + is_a_bc(bc) - 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) + local bc = BaseControl:new() + bc:finalize({}) + is_a_bc(bc) end --- }}} --- Networking {{{ -function test_hello() - local bc1 = BaseControl({hello1=true}, {hello1v=function() end}) - local bc2 = BaseControl({hello2=true}, {hello2v=function() end}) +function test_register() + local bc = BaseControl:new() + bc:register("register1", 1234) + bc:finalize() - assert_true(bc1:has_noun("hello1"), "Announcement did not come through") - 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") + -- Register after finalizing + local bc = BaseControl:finalize() + assert_error_match("after finalizing", function() + bc:register("register2", 4321) + end) - assert_true(bc1:has_verb("hello1v"), "Anverbcement did not come through") - assert_true(bc2:has_verb("hello1v"), "Anverbcement did not come through") - assert_true(bc1:has_verb("hello2v"), "Anverbcement did not come through") - assert_true(bc2:has_verb("hello2v"), "Anverbcement did not come through") + -- Register exisiting + local bc = BaseControl:new() + bc:register("register3", 123) + assert_error_match("already registered", function() + bc:register("register3", 12) + end) 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} +function test_available() + local bc = BaseControl:new() + bc:register("available1", 1234) + bc:register("available2v", function() end) + bc:finalize() - 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 + assert_equal(1234, bc.locals.nouns["available1"]) + assert_true(bc.locals.verbs["available2v"] ~= nil) 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_get() + local bc = BaseControl:new() + bc:register("get1", 1234) + bc:register("get2v", function() end) + bc:finalize() -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 + 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_two_way_verb() - local bc1 = BaseControl(nil, {twoway1 = function(bc, _, verb, par) - bc:call_verb(verb, par * 2) - end}) - local addr1 = network.get_scene() +function test_set() + local bc = BaseControl:new() + bc:register("set1", 31415) + bc:register("set2v", function() end) + bc:finalize() - local result - local bc2 = BaseControl(nil, {twoway2 = function(bc, _, res) - result = res - end}) - local addr2 = network.get_scene() + assert_equal(31415, bc:get("set1"), "wrong value for noun") + bc:set("set1", 14142) + assert_equal(14142, bc:get("set1"), "wrong value for noun") - 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 + assert_error_match("not a local", function() + bc:set("set2", 12345) 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) + assert_error_match("can't be cast into a noun", function() + bc:set("set2v", 12345) + end) - bc1:set_noun("listen1", 1234) -end + assert_pass(function() + bc:set("set2v", function() end) + 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 + assert_error_match("can't be cast into a verb", function() + bc:set("set1", function() end) + 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 +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() - 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() + bc:call("call1", 1, 2) + assert_equal(3, flag1, "call failed") + assert_equal(1, flag2, "call failed") - 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 + bc:call("call1", 10, 10) + assert_equal(20, flag1, "call failed") + assert_equal(2, flag2, "call failed") 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") +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() - network.set_scene(addr1) - bc1:cleanup() + 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") - network.set_scene(addr2) - assert_equal(nil, bc2:get_noun("cleanup1"), "Cleanup failed") - assert_false(bc2:has_noun("cleanup1"), "Cleanup failed") + 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_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) +function test_iter_empty() + local bc = BaseControl:new( + BaseControl.Network(31211) -- Network isolation + ):finalize() - 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") + assert_equal(0, #bc:nouns(), "nouns found in empty network") + assert_equal(0, #bc:verbs(), "verbs found in empty network") 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 +-- Network {{{ +function test_multinode() + local bc1 = BaseControl() + bc1:register("multinode1", 123) + bc1:register("multinode2v", function() end) + bc1:finalize() -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 + local bc2 = BaseControl:finalize() -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") + assert_true(bc2:has_noun("multinode1"), "remote noun missing") + assert_true(bc2:has_verb("multinode2v"), "remote verb missing") end -- }}} diff --git a/bc.lua b/bc.lua index 65a0f095deab..f6037877a8bc 100644 --- a/bc.lua +++ b/bc.lua @@ -1,261 +1,184 @@ -local component = require("component") -local event = require("event") -local serializer = require("serialization") +local serialization = require("serialization") local uuid = require("uuid") -local BC_VERSION = {0, 1} -local BC_PORT = 0xBC00 | (BC_VERSION[1] << 4) | BC_VERSION[2] +local Version = {0, 1} local Message = { Hello = 0x48454c4f, - Register = 0x00524547, - Disband = 0x44524547, + Register = 0x52454749, + Deregister = 0x44524547, NounRequest = 0x4e524551, NounResponse = 0x4e524553, VerbRequest = 0x56524551, + VerbResponse = 0x56524553, ListenRequest = 0x4c524551, ListenNotify = 0x4c4e4f54, ListenCancel = 0x4c535450, } -local Event = { - Change = 1, - Rising = 2, - Falling = 3, - Equals = 4, - Above = 5, - Below = 6, +local function query_param(self, v) + return {ty=self.ty, v=v} +end +local Query = { + Change = {ty=0x434847}, + Rising = {ty=0x524953}, + 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 = { - _version = BC_VERSION, - _default_port = BC_PORT, - Event = Event, - Message = Message, +-- Network ---------------------------------------------------------------- {{{ +local Network = { + default_port = 0xBC00 | (Version[1] << 4) | Version[2], } -bc.__index = bc --- Helpers -local function send_msg(self, remote, msg_table) - self.modem.send(remote, self.port, serializer.serialize(msg_table)) +function Network:new(modem, port) + if type(modem) == "number" then + port = modem + modem = nil + end + local self = { + modem = modem or require("component").modem, + port = port or Network.default_port, + } + setmetatable(self, {__index=Network}) + + return self end +setmetatable(Network, {__call=Network.new}) -local function broadcast_msg(self, msg_table) - self.modem.broadcast(self.port, serializer.serialize(msg_table)) +function Network:start(callback) + self.modem.open(self.port) + self.listener = function(_, addr_lo, addr_remote, port, _, msg) + -- Filter everything we don't care about + if addr_lo ~= self.modem.address then return end + if port ~= self.port then return end + callback(addr_remote, serialization.unserialize(msg)) + end + require("event").listen("modem_message", self.listener) end -function bc:has_noun(noun) - return self.local_nouns[noun] ~= nil or self.remote_nouns[noun] ~= nil +function Network:send(addr, msg) + self.modem.send(addr, self.port, serialization.serialize(msg)) 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 +function Network:broadcast(msg) + self.modem.broadcast(self.port, serialization.serialize(msg)) 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 +function Network:stop() + require("event").ignore("modem_message", self.listener) + self.modem.close(self.port) +end +-- }}} - value = msg.value - return true - end) - return value - else -- Not found at all - return nil - 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 end -function bc:listen(noun, event, evparam, callback) - -- You can leave out evparam - if type(evparam) == "function" then - callback = evparam - evparam = nil +function BaseControl:register(name, value) + if self.live then + error("can't register \""..name.."\" after finalizing") end - local remote_addr = self.remote_nouns[noun] - if remote_addr == nil then - error("Noun \""..noun.."\" is not listenable!") + if type(value) == "function" then + if self.locals.verbs[name] ~= nil then + error("\""..name.."\" already registered") + else + self.locals.verbs[name] = value + end 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 + if self.locals.nouns[name] ~= nil then + error("\""..name.."\" already registered") + else + self.locals.nouns[name] = value + end 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 +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 -function bc:has_verb(verb) - return self.local_verbs[verb] ~= nil or self.remote_verbs[verb] ~= nil +-- Nouns {{{ +function BaseControl:has_noun(noun) + return self.locals.nouns[noun] ~= nil or self.remotes.nouns[noun] ~= 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!") +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 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, - }) +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 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 +function BaseControl:set(name, value) + error("set unimplemented") end +-- }}} -local function new(_, local_nouns, local_verbs, overrides) - local self = { - local_nouns=local_nouns or {}, - local_verbs=local_verbs or {}, - remote_nouns={}, - remote_verbs={}, - - local_listeners={}, - remote_listeners={}, - - port=BC_PORT, - modem=component.modem, - timeout=5, - } - if overrides ~= nil then - for name, value in pairs(overrides) do - self[name] = value - end - end - setmetatable(self, bc) - - for noun in pairs(self.local_nouns) do - self.remote_listeners[noun] = {} - end - - self._modem_listener = function(_, _, remote_addr, port, _, msg) - if port ~= self.port then -- Ignore other ports - return - end +-- Verbs {{{ +function BaseControl:has_verb(verb) + return self.locals.verbs[verb] ~= nil or self.remotes.verbs[verb] ~= nil +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 +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 - -- Setup connection and say hello - self.modem.open(self.port) - event.listen("modem_message", self._modem_listener) - broadcast_msg(self, {ty=BC_MESSAGE.HELLO}) - local nouns, verbs = self:locals() - broadcast_msg(self, { - ty=BC_MESSAGE.REGISTER, - nouns=nouns, - verbs=verbs, - }) - - return self +function BaseControl:call(verb, ...) + error("call unimplemented") end +-- }}} +-- }}} -return setmetatable(bc, {__call=new, __index={new=new}}) +return setmetatable(BaseControl, {__call=BaseControl.new})