diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index d7e58f7..486be82 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -435,8 +435,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range ---@param packet mgmt_frame|crdn_frame|capi_frame|nil function public.handle_packet(packet) if packet ~= nil then - local l_port = packet.scada_frame.local_port() - local r_port = packet.scada_frame.remote_port() + local l_port = packet.scada_frame.local_channel() + local r_port = packet.scada_frame.remote_channel() local protocol = packet.scada_frame.protocol() if l_port == api_listen then diff --git a/coordinator/startup.lua b/coordinator/startup.lua index e5863c8..c93b3ec 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -37,9 +37,9 @@ local log_comms_connecting = coordinator.log_comms_connecting local cfv = util.new_validator() -cfv.assert_port(config.SCADA_SV_PORT) -cfv.assert_port(config.SCADA_SV_CTL_LISTEN) -cfv.assert_port(config.SCADA_API_LISTEN) +cfv.assert_channel(config.SCADA_SV_PORT) +cfv.assert_channel(config.SCADA_SV_CTL_LISTEN) +cfv.assert_channel(config.SCADA_API_LISTEN) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.SV_TIMEOUT) cfv.assert_min(config.SV_TIMEOUT, 2) diff --git a/pocket/config.lua b/pocket/config.lua index 27e1489..64705ab 100644 --- a/pocket/config.lua +++ b/pocket/config.lua @@ -1,11 +1,11 @@ local config = {} --- port of the SCADA supervisor -config.SCADA_SV_PORT = 16100 --- port for SCADA coordinator API access -config.SCADA_API_PORT = 16200 --- port to listen to incoming packets FROM servers -config.LISTEN_PORT = 16201 +-- supervisor access channel +config.SVR_CHANNEL = 16240 +-- coordinator access channel +config.CRD_CHANNEL = 16243 +-- pocket communication channel +config.PKT_CHANNEL = 16244 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 -- time in seconds (>= 2) before assuming a remote device is no longer active diff --git a/pocket/pocket.lua b/pocket/pocket.lua index cc4d870..a7482fd 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -18,22 +18,24 @@ local pocket = {} ---@nodiscard ---@param version string pocket version ---@param modem table modem device ----@param local_port integer local pocket port ----@param sv_port integer port of supervisor ----@param api_port integer port of coordinator API +---@param pkt_channel integer pocket comms channel +---@param svr_channel integer supervisor access channel +---@param crd_channel integer coordinator access channel ---@param range integer trusted device connection range ---@param sv_watchdog watchdog ---@param api_watchdog watchdog -function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_watchdog, api_watchdog) +function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog) local self = { sv = { linked = false, + addr = comms.BROADCAST, seq_num = 0, r_seq_num = nil, ---@type nil|integer last_est_ack = ESTABLISH_ACK.ALLOW }, api = { linked = false, + addr = comms.BROADCAST, seq_num = 0, r_seq_num = nil, ---@type nil|integer last_est_ack = ESTABLISH_ACK.ALLOW @@ -48,7 +50,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w -- configure modem channels local function _conf_channels() modem.closeAll() - modem.open(local_port) + modem.open(pkt_channel) end _conf_channels() @@ -61,9 +63,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w local pkt = comms.mgmt_packet() pkt.make(msg_type, msg) - s_pkt.make(self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) + s_pkt.make(self.sv.addr, self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) - modem.transmit(sv_port, local_port, s_pkt.raw_sendable()) + modem.transmit(svr_channel, pkt_channel, s_pkt.raw_sendable()) self.sv.seq_num = self.sv.seq_num + 1 end @@ -75,9 +77,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w local pkt = comms.mgmt_packet() pkt.make(msg_type, msg) - s_pkt.make(self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) + s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) - modem.transmit(api_port, local_port, s_pkt.raw_sendable()) + modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable()) self.api.seq_num = self.api.seq_num + 1 end @@ -89,9 +91,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w -- local pkt = comms.capi_packet() -- pkt.make(msg_type, msg) - -- s_pkt.make(self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable()) + -- s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable()) - -- modem.transmit(api_port, local_port, s_pkt.raw_sendable()) + -- modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable()) -- self.api.seq_num = self.api.seq_num + 1 -- end @@ -214,13 +216,13 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w ---@param packet mgmt_frame|capi_frame|nil function public.handle_packet(packet) if packet ~= nil then - local l_port = packet.scada_frame.local_port() - local r_port = packet.scada_frame.remote_port() + local l_chan = packet.scada_frame.local_channel() + local r_chan = packet.scada_frame.remote_channel() local protocol = packet.scada_frame.protocol() - if l_port ~= local_port then - log.debug("received packet on unconfigured channel " .. l_port, true) - elseif r_port == api_port then + if l_chan ~= pkt_channel then + log.debug("received packet on unconfigured channel " .. l_chan, true) + elseif r_chan == crd_channel then -- check sequence number if self.api.r_seq_num == nil then self.api.r_seq_num = packet.scada_frame.seq_num() @@ -304,7 +306,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w else log.debug("illegal packet type " .. protocol .. " from coordinator", true) end - elseif r_port == sv_port then + elseif r_chan == svr_channel then -- check sequence number if self.sv.r_seq_num == nil then self.sv.r_seq_num = packet.scada_frame.seq_num() @@ -388,7 +390,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w log.debug("illegal packet type " .. protocol .. " from supervisor", true) end else - log.debug("received packet from unconfigured channel " .. r_port, true) + log.debug("received packet from unconfigured channel " .. r_chan, true) end end end diff --git a/pocket/startup.lua b/pocket/startup.lua index 459dd8e..c1e9ccc 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -28,9 +28,9 @@ local println_ts = util.println_ts local cfv = util.new_validator() -cfv.assert_port(config.SCADA_SV_PORT) -cfv.assert_port(config.SCADA_API_PORT) -cfv.assert_port(config.LISTEN_PORT) +cfv.assert_channel(config.SVR_CHANNEL) +cfv.assert_channel(config.CRD_CHANNEL) +cfv.assert_channel(config.PKT_CHANNEL) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) cfv.assert_min(config.COMMS_TIMEOUT, 2) @@ -90,8 +90,8 @@ local function main() log.debug("startup> conn watchdogs created") -- start comms, open all channels - local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.LISTEN_PORT, config.SCADA_SV_PORT, - config.SCADA_API_PORT, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api) + local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.PKT_CHANNEL, config.SVR_CHANNEL, + config.CRD_CHANNEL, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api) log.debug("startup> comms init") -- base loop clock (2Hz, 10 ticks) diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index e402bbb..6845ec1 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -9,10 +9,10 @@ config.REACTOR_ID = 1 -- when emergency coolant is needed due to low coolant -- config.EMERGENCY_COOL = { side = "right", color = nil } --- port to send packets TO server -config.SERVER_PORT = 16000 --- port to listen to incoming packets FROM server -config.LISTEN_PORT = 14001 +-- supervisor access channel +config.SVR_CHANNEL = 16240 +-- PLC communication channel +config.PLC_CHANNEL = 16241 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 -- time in seconds (>= 2) before assuming a remote device is no longer active diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 4f04138..a4f66dc 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -446,14 +446,15 @@ end ---@param id integer reactor ID ---@param version string PLC version ---@param modem table modem device ----@param local_port integer local listening port ----@param server_port integer remote server port +---@param plc_channel integer PLC comms channel +---@param svr_channel integer supervisor server channel ---@param range integer trusted device connection range ---@param reactor table reactor device ---@param rps rps RPS reference ---@param conn_watchdog watchdog watchdog reference -function plc.comms(id, version, modem, local_port, server_port, range, reactor, rps, conn_watchdog) +function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor, rps, conn_watchdog) local self = { + sv_addr = comms.BROADCAST, seq_num = 0, r_seq_num = nil, scrammed = false, @@ -472,7 +473,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- configure modem channels local function _conf_channels() modem.closeAll() - modem.open(local_port) + modem.open(plc_channel) end _conf_channels() @@ -485,9 +486,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local r_pkt = comms.rplc_packet() r_pkt.make(id, msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) - modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -499,9 +500,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -667,6 +668,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- unlink from the server function public.unlink() + self.sv_addr = comms.BROADCAST self.linked = false self.r_seq_num = nil self.status_cache = nil @@ -731,7 +733,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, end end - -- parse an RPLC packet + -- parse a packet ---@nodiscard ---@param side string ---@param sender integer @@ -760,14 +762,14 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, pkt = mgmt_pkt.get() end else - log.debug("illegal packet type " .. s_pkt.protocol(), true) + log.debug("unsupported packet type " .. s_pkt.protocol(), true) end end return pkt end - -- handle an RPLC packet + -- handle RPLC and MGMT packets ---@param packet rplc_frame|mgmt_frame packet frame ---@param plc_state plc_state PLC state ---@param setpoints setpoints setpoint control table @@ -775,10 +777,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- print a log message to the terminal as long as the UI isn't running local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end - local l_port = packet.scada_frame.local_port() + local l_chan = packet.scada_frame.local_channel() + local src_addr = packet.scada_frame.src_addr() -- handle packets now that we have prints setup - if l_port == local_port then + if l_chan == plc_channel then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() @@ -797,7 +800,8 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- handle packet if protocol == PROTOCOL.RPLC then ---@cast packet rplc_frame - if self.linked then + -- if linked, only accept packets from configured supervisor + if self.linked and (self.sv_addr == src_addr) then if packet.type == RPLC_TYPE.STATUS then -- request of full status, clear cache first self.status_cache = nil @@ -928,15 +932,18 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, else log.debug("received unknown RPLC packet type " .. packet.type) end - else + elseif not self.linked then log.debug("discarding RPLC packet before linked") + else + log.debug("discarding RPLC from different supervisor (src_addr " .. src_addr .. " ≠ " .. self.sv_addr .. "sv_addr)") end elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame - if self.linked then + -- if linked, only accept packets from configured supervisor + if self.linked and (self.sv_addr == src_addr) then if packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- link request confirmation - if packet.length == 1 then + if (packet.length == 1) and (self.sv_addr == src_addr) then log.debug("received unsolicited establish response") local est_ack = packet.data[1] @@ -945,22 +952,26 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, self.status_cache = nil _send_struct() public.send_status(plc_state.no_reactor, plc_state.reactor_formed) - log.debug("re-sent initial status data") - elseif est_ack == ESTABLISH_ACK.DENY then - println_ts("received unsolicited link denial, unlinking") - log.warning("unsolicited establish request denied") - elseif est_ack == ESTABLISH_ACK.COLLISION then - println_ts("received unsolicited link collision, unlinking") - log.warning("unsolicited establish request collision") - elseif est_ack == ESTABLISH_ACK.BAD_VERSION then - println_ts("received unsolicited link version mismatch, unlinking") - log.warning("unsolicited establish request version mismatch") + log.debug("re-sent initial status data due to re-establish") else - println_ts("invalid unsolicited link response") - log.debug("unsolicited unknown establish request response") - end + if est_ack == ESTABLISH_ACK.DENY then + println_ts("received unsolicited link denial, unlinking") + log.warning("unsolicited establish request denied") + elseif est_ack == ESTABLISH_ACK.COLLISION then + println_ts("received unsolicited link collision, unlinking") + log.warning("unsolicited establish request collision") + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + println_ts("received unsolicited link version mismatch, unlinking") + log.warning("unsolicited establish request version mismatch") + else + println_ts("invalid unsolicited link response") + log.debug("unsolicited unknown establish request response") + end - self.linked = est_ack == ESTABLISH_ACK.ALLOW + -- unlink + self.sv_addr = comms.BROADCAST + self.linked = false + end -- clear this since this is for something that was unsolicited self.last_est_ack = ESTABLISH_ACK.ALLOW @@ -980,7 +991,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, log.warning("PLC KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end - -- log.debug("RPLC RTT = " .. trip_time .. "ms") + -- log.debug("PLC RTT = " .. trip_time .. "ms") _send_keep_alive_ack(timestamp) else @@ -1002,9 +1013,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, if est_ack == ESTABLISH_ACK.ALLOW then println_ts("linked!") - log.info("supervisor establish request approved, PLC is linked") + log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")") - -- reset remote sequence number and cache + -- link + reset remote sequence number and cache + self.sv_addr = src_addr + self.linked = true self.r_seq_num = nil self.status_cache = nil @@ -1012,23 +1025,28 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, public.send_status(plc_state.no_reactor, plc_state.reactor_formed) log.debug("sent initial status data") - elseif self.last_est_ack ~= est_ack then - if est_ack == ESTABLISH_ACK.DENY then - println_ts("link request denied, retrying...") - log.info("supervisor establish request denied, retrying") - elseif est_ack == ESTABLISH_ACK.COLLISION then - println_ts("reactor PLC ID collision (check config), retrying...") - log.warning("establish request collision, retrying") - elseif est_ack == ESTABLISH_ACK.BAD_VERSION then - println_ts("supervisor version mismatch (try updating), retrying...") - log.warning("establish request version mismatch, retrying") - else - println_ts("invalid link response, bad channel? retrying...") - log.error("unknown establish request response, retrying") + else + if self.last_est_ack ~= est_ack then + if est_ack == ESTABLISH_ACK.DENY then + println_ts("link request denied, retrying...") + log.info("supervisor establish request denied, retrying") + elseif est_ack == ESTABLISH_ACK.COLLISION then + println_ts("reactor PLC ID collision (check config), retrying...") + log.warning("establish request collision, retrying") + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + println_ts("supervisor version mismatch (try updating), retrying...") + log.warning("establish request version mismatch, retrying") + else + println_ts("invalid link response, bad channel? retrying...") + log.error("unknown establish request response, retrying") + end end + + -- unlink + self.sv_addr = comms.BROADCAST + self.linked = false end - self.linked = est_ack == ESTABLISH_ACK.ALLOW self.last_est_ack = est_ack -- report link state @@ -1044,7 +1062,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, log.error("illegal packet type " .. protocol, true) end else - log.debug("received packet on unconfigured channel " .. l_port, true) + log.debug("received packet on unconfigured channel " .. l_chan, true) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 680fdee..cf303c8 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.1.17" +local R_PLC_VERSION = "v1.3.0" local println = util.println local println_ts = util.println_ts @@ -31,8 +31,8 @@ local cfv = util.new_validator() cfv.assert_type_bool(config.NETWORKED) cfv.assert_type_int(config.REACTOR_ID) -cfv.assert_port(config.SERVER_PORT) -cfv.assert_port(config.LISTEN_PORT) +cfv.assert_channel(config.SVR_CHANNEL) +cfv.assert_channel(config.PLC_CHANNEL) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) cfv.assert_min(config.COMMS_TIMEOUT, 2) @@ -198,7 +198,7 @@ local function main() log.debug("init> conn watchdog started") -- start comms - smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, + smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.PLC_CHANNEL, config.SVR_CHANNEL, config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else diff --git a/rtu/config.lua b/rtu/config.lua index 1b96bec..c4eae91 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -2,11 +2,11 @@ local rsio = require("scada-common.rsio") local config = {} --- port to send packets TO server -config.SERVER_PORT = 16000 --- port to listen to incoming packets FROM server -config.LISTEN_PORT = 15001 --- max trusted modem message distance (< 1 to disable check) +-- supervisor access channel +config.SVR_CHANNEL = 16240 +-- RTU/MODBUS communication channel +config.RTU_CHANNEL = 16242 +-- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 -- time in seconds (>= 2) before assuming a remote device is no longer active config.COMMS_TIMEOUT = 5 diff --git a/rtu/rtu.lua b/rtu/rtu.lua index bb43706..c89dabb 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -159,12 +159,13 @@ end ---@nodiscard ---@param version string RTU version ---@param modem table modem device ----@param local_port integer local listening port ----@param server_port integer remote server port +---@param rtu_channel integer PLC comms channel +---@param svr_channel integer supervisor server channel ---@param range integer trusted device connection range ---@param conn_watchdog watchdog watchdog reference -function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog) +function rtu.comms(version, modem, rtu_channel, svr_channel, range, conn_watchdog) local self = { + sv_addr = comms.BROADCAST, seq_num = 0, r_seq_num = nil, txn_id = 0, @@ -180,7 +181,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- configure modem channels local function _conf_channels() modem.closeAll() - modem.open(local_port) + modem.open(rtu_channel) end _conf_channels() @@ -193,9 +194,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) - s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + modem.transmit(svr_channel, rtu_channel, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -238,8 +239,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog ---@param m_pkt modbus_packet function public.send_modbus(m_pkt) local s_pkt = comms.scada_packet() - s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) - modem.transmit(server_port, local_port, s_pkt.raw_sendable()) + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) + modem.transmit(svr_channel, rtu_channel, s_pkt.raw_sendable()) self.seq_num = self.seq_num + 1 end @@ -254,6 +255,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog ---@param rtu_state rtu_state function public.unlink(rtu_state) rtu_state.linked = false + self.sv_addr = comms.BROADCAST self.r_seq_num = nil end @@ -327,7 +329,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog -- print a log message to the terminal as long as the UI isn't running local function println_ts(message) if not rtu_state.fp_ok then util.println_ts(message) end end - if packet.scada_frame.local_port() == local_port then + if packet.scada_frame.local_channel() == rtu_channel then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() @@ -398,6 +400,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog if est_ack == ESTABLISH_ACK.ALLOW then -- establish allowed rtu_state.linked = true + self.sv_addr = packet.scada_frame.src_addr() self.r_seq_num = nil println_ts("supervisor connection established") log.info("supervisor connection established") diff --git a/rtu/startup.lua b/rtu/startup.lua index 3038310..366af28 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -42,8 +42,8 @@ local println_ts = util.println_ts local cfv = util.new_validator() -cfv.assert_port(config.SERVER_PORT) -cfv.assert_port(config.LISTEN_PORT) +cfv.assert_channel(config.SVR_CHANNEL) +cfv.assert_channel(config.RTU_CHANNEL) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.COMMS_TIMEOUT) cfv.assert_min(config.COMMS_TIMEOUT, 2) @@ -468,7 +468,7 @@ local function main() log.debug("startup> conn watchdog started") -- setup comms - smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT, + smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.RTU_CHANNEL, config.SVR_CHANNEL, config.TRUSTED_RANGE, smem_sys.conn_watchdog) log.debug("startup> comms init") diff --git a/scada-common/comms.lua b/scada-common/comms.lua index fb54101..41528dc 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -4,14 +4,17 @@ local log = require("scada-common.log") +local insert = table.insert + +---@diagnostic disable-next-line: undefined-field +local C_ID = os.getComputerID() ---@type integer computer ID + +local max_distance = nil ---@type integer|nil maximum acceptable transmission distance + ---@class comms local comms = {} -local insert = table.insert - -local max_distance = nil - -comms.version = "1.4.1" +comms.version = "2.0.0" ---@enum PROTOCOL local PROTOCOL = { @@ -122,6 +125,9 @@ comms.PLC_AUTO_ACK = PLC_AUTO_ACK comms.UNIT_COMMAND = UNIT_COMMAND comms.FAC_COMMAND = FAC_COMMAND +-- destination broadcast address (to all devices) +comms.BROADCAST = -1 + ---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet ---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame @@ -129,20 +135,18 @@ comms.FAC_COMMAND = FAC_COMMAND -- packets received with distances greater than this will be silently discarded ---@param distance integer max modem message distance (less than 1 disables the limit) function comms.set_trusted_range(distance) - if distance < 1 then - max_distance = nil - else - max_distance = distance - end + if distance < 1 then max_distance = nil else max_distance = distance end end -- generic SCADA packet object ---@nodiscard function comms.scada_packet() local self = { - modem_msg_in = nil, + modem_msg_in = nil, ---@type modem_message|nil valid = false, - raw = { -1, PROTOCOL.SCADA_MGMT, {} }, + raw = {}, + src_addr = comms.BROADCAST, + dest_addr = comms.BROADCAST, seq_num = -1, protocol = PROTOCOL.SCADA_MGMT, length = 0, @@ -153,34 +157,40 @@ function comms.scada_packet() local public = {} -- make a SCADA packet - ---@param seq_num integer + ---@param dest_addr integer destination computer address (ID) + ---@param seq_num integer sequence number ---@param protocol PROTOCOL ---@param payload table - function public.make(seq_num, protocol, payload) + function public.make(dest_addr, seq_num, protocol, payload) self.valid = true +---@diagnostic disable-next-line: undefined-field + self.src_addr = C_ID + self.dest_addr = dest_addr self.seq_num = seq_num self.protocol = protocol self.length = #payload self.payload = payload - self.raw = { self.seq_num, self.protocol, self.payload } + self.raw = { self.src_addr, self.dest_addr, self.seq_num, self.protocol, self.payload } end -- parse in a modem message as a SCADA packet ---@param side string modem side - ---@param sender integer sender port - ---@param reply_to integer reply port + ---@param sender integer sender channel + ---@param reply_to integer reply channel ---@param message any message body ---@param distance integer transmission distance ---@return boolean valid valid message received function public.receive(side, sender, reply_to, message, distance) + ---@class modem_message self.modem_msg_in = { iface = side, - s_port = sender, - r_port = reply_to, + s_channel = sender, + r_channel = reply_to, msg = message, dist = distance } + self.valid = false self.raw = self.modem_msg_in.msg if (type(max_distance) == "number") and (distance > max_distance) then @@ -188,20 +198,31 @@ function comms.scada_packet() -- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range") else if type(self.raw) == "table" then - if #self.raw >= 3 then - self.seq_num = self.raw[1] - self.protocol = self.raw[2] + if #self.raw == 5 then + self.src_addr = self.raw[1] + self.dest_addr = self.raw[2] + self.seq_num = self.raw[3] + self.protocol = self.raw[4] - -- element 3 must be a table - if type(self.raw[3]) == "table" then - self.length = #self.raw[3] - self.payload = self.raw[3] + -- element 5 must be a table + if type(self.raw[5]) == "table" then + self.length = #self.raw[5] + self.payload = self.raw[5] end + else + self.src_addr = nil + self.dest_addr = nil + self.seq_num = nil + self.protocol = nil + self.length = 0 + self.payload = {} end - self.valid = type(self.seq_num) == "number" and - type(self.protocol) == "number" and - type(self.payload) == "table" + -- check if this packet is destined for this device + local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == C_ID) + + self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and + type(self.seq_num) == "number" and type(self.protocol) == "number" and type(self.payload) == "table" end end @@ -216,13 +237,17 @@ function comms.scada_packet() function public.raw_sendable() return self.raw end ---@nodiscard - function public.local_port() return self.modem_msg_in.s_port end + function public.local_channel() return self.modem_msg_in.s_channel end ---@nodiscard - function public.remote_port() return self.modem_msg_in.r_port end + function public.remote_channel() return self.modem_msg_in.r_channel end ---@nodiscard function public.is_valid() return self.valid end + ---@nodiscard + function public.src_addr() return self.src_addr end + ---@nodiscard + function public.dest_addr() return self.dest_addr end ---@nodiscard function public.seq_num() return self.seq_num end ---@nodiscard diff --git a/scada-common/util.lua b/scada-common/util.lua index 063143c..e13d6bb 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -539,7 +539,7 @@ function util.new_validator() function public.assert_range(check, min, max) valid = valid and check >= min and check <= max end function public.assert_range_ex(check, min, max) valid = valid and check > min and check < max end - function public.assert_port(port) valid = valid and type(port) == "number" and port >= 0 and port <= 65535 end + function public.assert_channel(channel) valid = valid and type(channel) == "number" and channel >= 0 and channel <= 65535 end -- check if all assertions passed successfully ---@nodiscard diff --git a/supervisor/config.lua b/supervisor/config.lua index b716d38..a4a595b 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -1,9 +1,15 @@ local config = {} --- scada network listen for PLC's and RTU's -config.SCADA_DEV_LISTEN = 16000 --- listen port for SCADA supervisor access -config.SCADA_SV_CTL_LISTEN = 16100 +-- supervisor comms channel +config.SVR_CHANNEL = 16240 +-- PLC comms channel +config.PLC_CHANNEL = 16241 +-- RTU/MODBUS comms channel +config.RTU_CHANNEL = 16242 +-- coordinator comms channel +config.CRD_CHANNEL = 16243 +-- pocket comms channel +config.PKT_CHANNEL = 16244 -- max trusted modem message distance (0 to disable check) config.TRUSTED_RANGE = 0 -- time in seconds (>= 2) before assuming a remote device is no longer active diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 8ad3366..60db434 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -231,32 +231,25 @@ function svsessions.find_plc_session(remote_port) return session end --- find a PLC/RTU session by the remote port +-- find a coordinator session by the remote port ---@nodiscard ---@param remote_port integer ----@return plc_session_struct|rtu_session_struct|nil -function svsessions.find_device_session(remote_port) - -- check RTU sessions - local session = _find_session(self.sessions.rtu, remote_port) - - -- check PLC sessions - if session == nil then session = _find_session(self.sessions.plc, remote_port) end - ---@cast session plc_session_struct|rtu_session_struct|nil - +---@return coord_session_struct|nil +function svsessions.find_coord_session(remote_port) + -- check coordinator sessions + local session = _find_session(self.sessions.coord, remote_port) + ---@cast session coord_session_struct|nil return session end --- find a coordinator or diagnostic access session by the remote port +-- find a pocket diagnostics session by the remote port ---@nodiscard ---@param remote_port integer ----@return coord_session_struct|diag_session_struct|nil -function svsessions.find_svctl_session(remote_port) - -- check coordinator sessions - local session = _find_session(self.sessions.coord, remote_port) - +---@return diag_session_struct|nil +function svsessions.find_pdg_session(remote_port) -- check diagnostic sessions - if session == nil then session = _find_session(self.sessions.diag, remote_port) end - ---@cast session coord_session_struct|diag_session_struct|nil + local session = _find_session(self.sessions.diag, remote_port) + ---@cast session diag_session_struct|nil return session end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2e16777..156a8ec 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v0.15.7" +local SUPERVISOR_VERSION = "v0.16.0" local println = util.println local println_ts = util.println_ts @@ -25,8 +25,11 @@ local println_ts = util.println_ts local cfv = util.new_validator() -cfv.assert_port(config.SCADA_DEV_LISTEN) -cfv.assert_port(config.SCADA_SV_CTL_LISTEN) +cfv.assert_channel(config.SVR_CHANNEL) +cfv.assert_channel(config.PLC_CHANNEL) +cfv.assert_channel(config.RTU_CHANNEL) +cfv.assert_channel(config.CRD_CHANNEL) +cfv.assert_channel(config.PKT_CHANNEL) cfv.assert_type_int(config.TRUSTED_RANGE) cfv.assert_type_num(config.PLC_TIMEOUT) cfv.assert_min(config.PLC_TIMEOUT, 2) @@ -90,9 +93,17 @@ local function main() return end - -- start comms, open all channels - local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, - config.SCADA_DEV_LISTEN, config.SCADA_SV_CTL_LISTEN, config.TRUSTED_RANGE) + -- start comms + ---@class sv_channel_list + local channels = { + SVR = config.SVR_CHANNEL, + PLC = config.PLC_CHANNEL, + RTU = config.RTU_CHANNEL, + CRD = config.CRD_CHANNEL, + PKT = config.PKT_CHANNEL + } + local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, + channels, config.TRUSTED_RANGE) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 998c438..d467b97 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -19,11 +19,16 @@ local println = util.println ---@param num_reactors integer number of reactors ---@param cooling_conf table cooling configuration table ---@param modem table modem device ----@param dev_listen integer listening port for PLC/RTU devices ----@param svctl_listen integer listening port for supervisor access +---@param channels sv_channel_list network channels ---@param range integer trusted device connection range ---@diagnostic disable-next-line: unused-local -function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range) +function supervisor.comms(_version, num_reactors, cooling_conf, modem, channels, range) + local svr_channel = channels.SVR + local plc_channel = channels.PLC + local rtu_channel = channels.RTU + local crd_channel = channels.CRD + local pkt_channel = channels.PKT + local self = { last_est_acks = {} } @@ -35,8 +40,7 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste -- configure modem channels local function _conf_channels() modem.closeAll() - modem.open(dev_listen) - modem.open(svctl_listen) + modem.open(svr_channel) end _conf_channels() @@ -44,31 +48,19 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste -- link modem to svsessions svsessions.init(modem, num_reactors, cooling_conf) - -- send an establish request response to a PLC/RTU - ---@param dest integer - ---@param msg table - local function _send_dev_establish(seq_id, dest, msg) + -- send an establish request response + ---@param packet scada_packet + ---@param ack ESTABLISH_ACK + ---@param data? any optional data + local function _send_establish(packet, ack, data) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() - m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) - s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) + m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, { ack, data }) + s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - modem.transmit(dest, dev_listen, s_pkt.raw_sendable()) - end - - -- send supervisor control access connection establish response - ---@param seq_id integer - ---@param dest integer - ---@param msg table - local function _send_svctl_establish(seq_id, dest, msg) - local s_pkt = comms.scada_packet() - local c_pkt = comms.mgmt_packet() - - c_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg) - s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, c_pkt.raw_sendable()) - - modem.transmit(dest, svctl_listen, s_pkt.raw_sendable()) + modem.transmit(packet.remote_channel(), svr_channel, s_pkt.raw_sendable()) + self.last_est_acks[packet.src_addr()] = ack end -- PUBLIC FUNCTIONS -- @@ -136,17 +128,95 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil function public.handle_packet(packet) if packet ~= nil then - local l_port = packet.scada_frame.local_port() - local r_port = packet.scada_frame.remote_port() + local l_chan = packet.scada_frame.local_channel() + local r_chan = packet.scada_frame.remote_channel() + local s_addr = packet.scada_frame.src_addr() local protocol = packet.scada_frame.protocol() - -- device (RTU/PLC) listening channel - if l_port == dev_listen then + if l_chan ~= svr_channel then + log.debug("received packet on unconfigured channel " .. l_chan, true) + return + end + + if r_chan == plc_channel then + -- look for an associated session + local session = svsessions.find_plc_session(s_addr) + + if protocol == PROTOCOL.RPLC then + ---@cast packet rplc_frame + -- reactor PLC packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- unknown session, force a re-link + log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[s_addr] + + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PLC then + -- PLC linking request + if packet.length == 4 and type(packet.data[4]) == "number" then + local reactor_id = packet.data[4] + local plc_id = svsessions.establish_plc_session(l_chan, r_chan, reactor_id, firmware_v) + + if plc_id == false then + -- reactor already has a PLC assigned + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + else + -- got an ID; assigned to a reactor successfully + println(util.c("PLC (", firmware_v, ") [:", r_chan, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_chan, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + end + else + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC listening channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("invalid establish packet (on PLC listening channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c(r_chan, " -> ", l_chan, ": discarding SCADA_MGMT packet without a known session")) + end + end + elseif r_chan == rtu_channel then + -- look for an associated session + local session = svsessions.find_rtu_session(s_addr) + if protocol == PROTOCOL.MODBUS_TCP then ---@cast packet modbus_frame - -- look for an associated session - local session = svsessions.find_rtu_session(r_port) - -- MODBUS response if session ~= nil then -- pass the packet onto the session handler @@ -155,105 +225,59 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste -- any other packet should be session related, discard it log.debug("discarding MODBUS_TCP packet without a known session") end - elseif protocol == PROTOCOL.RPLC then - ---@cast packet rplc_frame - -- look for an associated session - local session = svsessions.find_plc_session(r_port) - - -- reactor PLC packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- unknown session, force a re-link - log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink") - _send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY }) - end elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame - -- look for an associated session - local session = svsessions.find_device_session(r_port) - -- SCADA management packet if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- establish a new session - local next_seq_id = packet.scada_frame.seq_num() + 1 + local last_ack = self.last_est_acks[s_addr] -- validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] + local comms_v = packet.data[1] local firmware_v = packet.data[2] - local dev_type = packet.data[3] + local dev_type = packet.data[3] if comms_v ~= comms.version then - if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping device establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) - elseif dev_type == DEVICE_TYPE.PLC then - -- PLC linking request - if packet.length == 4 and type(packet.data[4]) == "number" then - local reactor_id = packet.data[4] - local plc_id = svsessions.establish_plc_session(l_port, r_port, reactor_id, firmware_v) - - if plc_id == false then - -- reactor already has a PLC assigned - if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) - self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION - end - - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) - else - -- got an ID; assigned to a reactor successfully - println(util.c("PLC (", firmware_v, ") [:", r_port, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_port, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) - self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW - end - else - log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) - end + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.RTU then if packet.length == 4 then -- this is an RTU advertisement for a new session local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(l_port, r_port, rtu_advert, firmware_v) + local s_id = svsessions.establish_rtu_session(l_chan, r_chan, rtu_advert, firmware_v) - println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected")) - log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) - - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) + println(util.c("RTU (", firmware_v, ") [:", r_chan, "] \xbb connected")) + log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_chan, "] connected with session ID ", s_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug("RTU_ESTABLISH: packet length mismatch") - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC/RTU listening channel")) - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU listening channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug("invalid establish packet (on PLC/RTU listening channel)") - _send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + log.debug("invalid establish packet (on RTU listening channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it - log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session")) + log.debug(util.c(r_chan, " -> ", l_chan, ": discarding SCADA_MGMT packet without a known session")) end else - log.debug("illegal packet type " .. protocol .. " on device listening channel") + log.debug("illegal packet type " .. protocol .. " on RTU listening channel") end - -- coordinator listening channel - elseif l_port == svctl_listen then + elseif r_chan == crd_channel then -- look for an associated session - local session = svsessions.find_svctl_session(r_port) + local session = svsessions.find_svctl_session(s_addr) if protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame @@ -263,24 +287,23 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste session.in_queue.push_packet(packet) elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then -- establish a new session - local next_seq_id = packet.scada_frame.seq_num() + 1 + local last_ack = self.last_est_acks[s_addr] -- validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] + local comms_v = packet.data[1] local firmware_v = packet.data[2] - local dev_type = packet.data[3] + local dev_type = packet.data[3] if comms_v ~= comms.version then - if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION end - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.CRDN then -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v) + local s_id = svsessions.establish_coord_session(l_chan, r_chan, firmware_v) if s_id ~= false then local config = { num_reactors } @@ -289,39 +312,36 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste table.insert(config, cooling_conf[i].TURBINES) end - println(util.c("CRD (", firmware_v, ") [:", r_port, "] \xbb connected")) - log.info(util.c("SVCTL_ESTABLISH: coordinator (", firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + println(util.c("CRD (", firmware_v, ") [@", s_addr, "] \xbb connected")) + log.info(util.c("ESTABLISH: coordinator (", firmware_v, ") [@", s_addr, "] connected with session ID ", s_id)) - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config }) - self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, config) else - if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then - log.info("SVCTL_ESTABLISH: denied new coordinator due to already being connected to another coordinator") - self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.info("ESTABLISH: denied new coordinator [@" .. s_addr .. "] due to already being connected to another coordinator") end - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) end elseif dev_type == DEVICE_TYPE.PKT then -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_diag_session(l_port, r_port, firmware_v) + local s_id = svsessions.establish_diag_session(l_chan, r_chan, firmware_v) - println(util.c("PKT (", firmware_v, ") [:", r_port, "] \xbb connected")) - log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", s_id)) + println(util.c("PKT (", firmware_v, ") [:", r_chan, "] \xbb connected")) + log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_chan, "] connected with session ID ", s_id)) - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW }) - self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug(util.c("illegal establish packet for device ", dev_type, " on SVCTL listening channel")) - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("SVCTL_ESTABLISH: establish packet length mismatch") - _send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it - log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session") + log.debug(r_chan .. " -> " .. l_chan .. ": discarding SCADA_MGMT packet without a known session") end elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame @@ -331,13 +351,15 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste session.in_queue.push_packet(packet) else -- any other packet should be session related, discard it - log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_CRDN packet without a known session") + log.debug(r_chan .. "->" .. l_chan .. ": discarding SCADA_CRDN packet without a known session") end else log.debug("illegal packet type " .. protocol .. " on coordinator listening channel") end + elseif r_chan == pkt_channel then + else - log.debug("received packet on unconfigured channel " .. l_port, true) + log.debug("received packet for unknown channel " .. r_chan, true) end end end diff --git a/test/lockbox-benchmark.lua b/test/lockbox-benchmark.lua index 198f41b..7c6ae55 100644 --- a/test/lockbox-benchmark.lua +++ b/test/lockbox-benchmark.lua @@ -38,7 +38,7 @@ local pkt = comms.modbus_packet() ---@diagnostic disable-next-line: param-type-mismatch pkt.make(1, 2, 7, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) local spkt = comms.scada_packet() -spkt.make(1, 1, pkt.raw_sendable()) +spkt.make(0, 1, 1, pkt.raw_sendable()) start = util.time() local data = textutils.serialize(spkt.raw_sendable(), { allow_repetitions = true, compact = true }) diff --git a/test/turbine_modbustest.lua b/test/turbine_modbustest.lua index fe167d7..c8f7801 100644 --- a/test/turbine_modbustest.lua +++ b/test/turbine_modbustest.lua @@ -22,7 +22,7 @@ println("") -- RTU init -- -log.init("/log.txt", log.MODE.NEW) +log.init("/log.txt", log.MODE.NEW, true) print(">>> init turbine RTU: ")