diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index f6a2018..9e98a33 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -232,8 +232,7 @@ function coordinator.comms(version, nic, sv_watchdog) local self = { sv_linked = false, sv_addr = comms.BROADCAST, - sv_seq_num = 0, - sv_r_seq_num = nil, + sv_seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate sv_config_err = false, last_est_ack = ESTABLISH_ACK.ALLOW, last_api_est_acks = {}, @@ -370,7 +369,6 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false - self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {}) end @@ -492,7 +490,7 @@ function coordinator.comms(version, nic, sv_watchdog) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- pocket linking request - local id = apisessions.establish_session(src_addr, firmware_v) + local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num() + 1, firmware_v) coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id)) local conf = iocontrol.get_db().facility.conf @@ -514,16 +512,14 @@ function coordinator.comms(version, nic, sv_watchdog) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv_r_seq_num == nil then - self.sv_r_seq_num = packet.scada_frame.seq_num() - elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.sv_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return false elseif self.sv_linked and src_addr ~= self.sv_addr then log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?") return false else - self.sv_r_seq_num = packet.scada_frame.seq_num() + self.sv_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -675,7 +671,6 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false - self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) log.info("server connection closed by remote host") else @@ -706,7 +701,6 @@ function coordinator.comms(version, nic, sv_watchdog) self.sv_addr = src_addr self.sv_linked = true - self.sv_r_seq_num = nil self.sv_config_err = false iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED) diff --git a/coordinator/session/apisessions.lua b/coordinator/session/apisessions.lua index 516b91b..4daa45d 100644 --- a/coordinator/session/apisessions.lua +++ b/coordinator/session/apisessions.lua @@ -89,10 +89,11 @@ end -- establish a new API session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer pocket computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string pocket version ---@return integer session_id -function apisessions.establish_session(source_addr, version) +function apisessions.establish_session(source_addr, i_seq_num, version) ---@class pkt_session_struct local pkt_s = { open = true, @@ -105,7 +106,7 @@ function apisessions.establish_session(source_addr, version) local id = self.next_id - pkt_s.instance = pocket.new_session(id, source_addr, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout) + pkt_s.instance = pocket.new_session(id, source_addr, i_seq_num, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout) table.insert(self.sessions, pkt_s) local mt = { diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index cd03fc1..f2f59e4 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -32,16 +32,16 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout -function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) +function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local log_header = "pkt_session(" .. id .. "): " local self = { -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -104,13 +104,11 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f152bc8..9e20631 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.7" +local COORDINATOR_VERSION = "v1.5.0" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 5e6798e..fb4f41a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -370,15 +370,13 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv = { linked = false, addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, ---@type nil|integer + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate last_est_ack = ESTABLISH_ACK.ALLOW }, api = { linked = false, addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, ---@type nil|integer + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate last_est_ack = ESTABLISH_ACK.ALLOW }, establish_delay_counter = 0 @@ -466,7 +464,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST _send_sv(MGMT_TYPE.CLOSE, {}) end @@ -476,7 +473,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.r_seq_num = nil self.api.addr = comms.BROADCAST _send_crd(MGMT_TYPE.CLOSE, {}) end @@ -603,17 +599,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.CRD_Channel then -- check sequence number - if self.api.r_seq_num == nil then - self.api.r_seq_num = packet.scada_frame.seq_num() - elseif self.connected and ((self.api.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.api.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (API): last = " .. self.api.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.api.linked and (src_addr ~= self.api.addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr .. "); channel in use by another system?") return else - self.api.r_seq_num = packet.scada_frame.seq_num() + self.api.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -658,7 +652,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.r_seq_num = nil self.api.addr = comms.BROADCAST log.info("coordinator server connection closed by remote host") else _fail_type(packet) end @@ -723,17 +716,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv.r_seq_num == nil then - self.sv.r_seq_num = packet.scada_frame.seq_num() - elseif self.connected and ((self.sv.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (SVR): last = " .. self.sv.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.sv.linked and (src_addr ~= self.sv.addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr .. "); channel in use by another system?") return else - self.sv.r_seq_num = packet.scada_frame.seq_num() + self.sv.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -764,7 +755,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST log.info("supervisor server connection closed by remote host") elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then diff --git a/pocket/startup.lua b/pocket/startup.lua index a80a4ca..2701efb 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -20,7 +20,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.10.0-alpha" +local POCKET_VERSION = "v0.11.0-alpha" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 2fb869a..df116cf 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -524,8 +524,7 @@ end function plc.comms(version, nic, reactor, rps, conn_watchdog) local self = { sv_addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate scrammed = false, linked = false, last_est_ack = ESTABLISH_ACK.ALLOW, @@ -725,7 +724,6 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) function public.unlink() self.sv_addr = comms.BROADCAST self.linked = false - self.r_seq_num = nil self.status_cache = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -834,17 +832,15 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- handle packets now that we have prints setup if l_chan == config.PLC_Channel then -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = packet.scada_frame.seq_num() - elseif self.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.r_seq_num = packet.scada_frame.seq_num() + self.seq_num = packet.scada_frame.seq_num() + 1 end -- feed the watchdog first so it doesn't uhh...eat our packets :) @@ -1030,10 +1026,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) println_ts("linked!") log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")") - -- link + reset remote sequence number and cache + -- link + reset cache self.sv_addr = src_addr self.linked = true - self.r_seq_num = nil self.status_cache = nil if plc_state.reactor_formed then _send_struct() end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1b8b2d6..6f6a27b 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.7.11" +local R_PLC_VERSION = "v1.8.0" local println = util.println local println_ts = util.println_ts diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 71cea40..0278f42 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -284,8 +284,7 @@ end function rtu.comms(version, nic, conn_watchdog) local self = { sv_addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate txn_id = 0, last_est_ack = ESTABLISH_ACK.ALLOW } @@ -363,7 +362,6 @@ function rtu.comms(version, nic, conn_watchdog) function public.unlink(rtu_state) rtu_state.linked = false self.sv_addr = comms.BROADCAST - self.r_seq_num = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -441,17 +439,15 @@ function rtu.comms(version, nic, conn_watchdog) if l_chan == config.RTU_Channel then -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = packet.scada_frame.seq_num() - elseif rtu_state.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif rtu_state.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.r_seq_num = packet.scada_frame.seq_num() + self.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -556,7 +552,6 @@ function rtu.comms(version, nic, conn_watchdog) -- 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") else diff --git a/rtu/startup.lua b/rtu/startup.lua index ae2e72b..00f27c7 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.9.6" +local RTU_VERSION = "v1.10.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 3e65ed6..e31c60b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "2.5.2" +comms.version = "3.0.0" comms.api_version = "0.0.3" ---@enum PROTOCOL @@ -240,6 +240,8 @@ function comms.scada_packet() ---@nodiscard function public.modem_event() return self.modem_msg_in end ---@nodiscard + function public.raw_header() return { self.src_addr, self.dest_addr, self.seq_num, self.protocol } end + ---@nodiscard function public.raw_sendable() return self.raw end ---@nodiscard @@ -278,7 +280,7 @@ function comms.authd_packet() src_addr = comms.BROADCAST, dest_addr = comms.BROADCAST, mac = "", - payload = "" + payload = nil } ---@class authd_packet @@ -286,14 +288,13 @@ function comms.authd_packet() -- make an authenticated SCADA packet ---@param s_packet scada_packet scada packet to authenticate - ---@param mac function message authentication function + ---@param mac function message authentication hash function function public.make(s_packet, mac) self.valid = true self.src_addr = s_packet.src_addr() self.dest_addr = s_packet.dest_addr() - self.payload = textutils.serialize(s_packet.raw_sendable(), { allow_repetitions = true, compact = true }) - self.mac = mac(self.payload) - self.raw = { self.src_addr, self.dest_addr, self.mac, self.payload } + self.mac = mac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) + self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.data() } end -- parse in a modem message as an authenticated SCADA packet @@ -330,14 +331,14 @@ function comms.authd_packet() self.src_addr = nil self.dest_addr = nil self.mac = "" - self.payload = "" + self.payload = {} end -- check if this packet is destined for this device local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == COMPUTER_ID) self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and - type(self.mac) == "string" and type(self.payload) == "string" + type(self.mac) == "string" and type(self.payload) == "table" end end diff --git a/scada-common/network.lua b/scada-common/network.lua index bdaf5c3..4dce34e 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -114,7 +114,7 @@ function network.nic(modem) modem.open(channel) end - -- link all public functions except for transmit + -- link all public functions except for transmit, open, and close for key, func in pairs(modem) do if key ~= "transmit" and key ~= "open" and key ~= "close" and key ~= "closeAll" then public[key] = func end end @@ -184,7 +184,7 @@ function network.nic(modem) ---@cast tx_packet authd_packet tx_packet.make(packet, compute_hmac) - -- log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") + -- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") end modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable()) @@ -211,17 +211,18 @@ function network.nic(modem) a_packet.receive(side, sender, reply_to, message, distance) if a_packet.is_valid() then - -- local start = util.time_ms() - local packet_hmac = a_packet.mac() - local msg = a_packet.data() - local computed_hmac = compute_hmac(msg) + s_packet.receive(side, sender, reply_to, a_packet.data(), distance) - if packet_hmac == computed_hmac then - -- log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") - s_packet.receive(side, sender, reply_to, textutils.unserialize(msg), distance) + if s_packet.is_valid() then + -- local start = util.time_ms() + local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) + + if a_packet.mac() == computed_hmac then + -- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") s_packet.stamp_authenticated() else - -- log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + -- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + end end end else diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 9c9284f..f449650 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -43,12 +43,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facility, fp_ok) +function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, facility, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -57,8 +58,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil local self = { units = facility.get_units(), -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), establish_time = util.time_s(), @@ -182,13 +182,11 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a4a8271..63293e4 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -48,12 +48,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param reactor_id integer reactor ID ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param fp_ok boolean if the front panel UI is running -function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, fp_ok) +function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -66,8 +67,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f ramping_rate = false, auto_lock = false, -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, received_struct = false, received_status_cache = false, @@ -309,13 +309,11 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f ---@param pkt mgmt_frame|rplc_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- process packet diff --git a/supervisor/session/pocket.lua b/supervisor/session/pocket.lua index 48756ea..145add2 100644 --- a/supervisor/session/pocket.lua +++ b/supervisor/session/pocket.lua @@ -30,12 +30,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, fp_ok) +function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, facility, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -43,8 +44,7 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, local self = { -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -93,13 +93,11 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, ---@param pkt mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 6bebb87..789e649 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -34,13 +34,14 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param advertisement table RTU device advertisement ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement, facility, fp_ok) +function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, advertisement, facility, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -51,8 +52,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement advert = advertisement, fac_units = facility.get_units(), -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -240,13 +240,11 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement ---@param pkt modbus_frame|mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 7300162..c7c24fc 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -273,11 +273,12 @@ end -- establish a new PLC session ---@nodiscard ----@param source_addr integer ----@param for_reactor integer ----@param version string +---@param source_addr integer PLC computer ID +---@param i_seq_num integer initial sequence number to use next +---@param for_reactor integer unit ID +---@param version string PLC version ---@return integer|false session_id -function svsessions.establish_plc_session(source_addr, for_reactor, version) +function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then ---@class plc_session_struct local plc_s = { @@ -294,7 +295,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) local id = self.next_ids.plc - plc_s.instance = plc.new_session(id, source_addr, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) + plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) table.insert(self.sessions.plc, plc_s) local units = self.facility.get_units() @@ -320,13 +321,14 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) end end --- establish a new RTU session +-- establish a new RTU gateway session ---@nodiscard ----@param source_addr integer ----@param advertisement table ----@param version string +---@param source_addr integer RTU gateway computer ID +---@param i_seq_num integer initial sequence number to use next +---@param advertisement table RTU capability advertisement +---@param version string RTU gateway version ---@return integer session_id -function svsessions.establish_rtu_session(source_addr, advertisement, version) +function svsessions.establish_rtu_session(source_addr, i_seq_num, advertisement, version) ---@class rtu_session_struct local rtu_s = { s_type = "rtu", @@ -341,7 +343,7 @@ function svsessions.establish_rtu_session(source_addr, advertisement, version) local id = self.next_ids.rtu - rtu_s.instance = rtu.new_session(id, source_addr, rtu_s.in_queue, rtu_s.out_queue, self.config.RTU_Timeout, advertisement, self.facility, self.fp_ok) + rtu_s.instance = rtu.new_session(id, source_addr, i_seq_num, rtu_s.in_queue, rtu_s.out_queue, self.config.RTU_Timeout, advertisement, self.facility, self.fp_ok) table.insert(self.sessions.rtu, rtu_s) local mt = { @@ -362,10 +364,11 @@ end -- establish a new coordinator session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer coordinator computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string coordinator version ---@return integer|false session_id -function svsessions.establish_crd_session(source_addr, version) +function svsessions.establish_crd_session(source_addr, i_seq_num, version) if svsessions.get_crd_session() == nil then ---@class crd_session_struct local crd_s = { @@ -381,7 +384,7 @@ function svsessions.establish_crd_session(source_addr, version) local id = self.next_ids.crd - crd_s.instance = coordinator.new_session(id, source_addr, crd_s.in_queue, crd_s.out_queue, self.config.CRD_Timeout, self.facility, self.fp_ok) + crd_s.instance = coordinator.new_session(id, source_addr, i_seq_num, crd_s.in_queue, crd_s.out_queue, self.config.CRD_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.crd, crd_s) local mt = { @@ -406,10 +409,11 @@ end -- establish a new pocket diagnostics session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer pocket computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string pocket version ---@return integer|false session_id -function svsessions.establish_pdg_session(source_addr, version) +function svsessions.establish_pdg_session(source_addr, i_seq_num, version) ---@class pdg_session_struct local pdg_s = { s_type = "pkt", @@ -424,7 +428,7 @@ function svsessions.establish_pdg_session(source_addr, version) local id = self.next_ids.pdg - pdg_s.instance = pocket.new_session(id, source_addr, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok) + pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.pdg, pdg_s) local mt = { diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 89c38d8..edc60f1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.12" +local SUPERVISOR_VERSION = "v1.4.0" local println = util.println local println_ts = util.println_ts @@ -214,7 +214,7 @@ local function main() elseif event == "modem_message" then -- got a packet local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5) - superv_comms.handle_packet(packet) + if packet then superv_comms.handle_packet(packet) end elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then -- handle a mouse event diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 1d79e72..2d69b7c 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -191,283 +191,282 @@ function supervisor.comms(_version, nic, fp_ok) end -- handle a packet - ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil + ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) - if packet ~= nil then - local l_chan = packet.scada_frame.local_channel() - local r_chan = packet.scada_frame.remote_channel() - local src_addr = packet.scada_frame.src_addr() - local protocol = packet.scada_frame.protocol() + local l_chan = packet.scada_frame.local_channel() + local r_chan = packet.scada_frame.remote_channel() + local src_addr = packet.scada_frame.src_addr() + local protocol = packet.scada_frame.protocol() + local i_seq_num = packet.scada_frame.seq_num() + 1 - if l_chan ~= config.SVR_Channel then - log.debug("received packet on unconfigured channel " .. l_chan, true) - elseif r_chan == config.PLC_Channel then - -- look for an associated session - local session = svsessions.find_plc_session(src_addr) + if l_chan ~= config.SVR_Channel then + log.debug("received packet on unconfigured channel " .. l_chan, true) + elseif r_chan == config.PLC_Channel then + -- look for an associated session + local session = svsessions.find_plc_session(src_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 - -- any other packet should be session related, discard it - log.debug("discarding RPLC packet without a known session") - 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 == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_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(src_addr, 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, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] 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 channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug("invalid establish packet (on PLC channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) - end + 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 - log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) + -- any other packet should be session related, discard it + log.debug("discarding RPLC packet without a known session") end - elseif r_chan == config.RTU_Channel then - -- look for an associated session - local session = svsessions.find_rtu_session(src_addr) + 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 == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_addr] - if protocol == PROTOCOL.MODBUS_TCP then - ---@cast packet modbus_frame - -- MODBUS response - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding MODBUS_TCP packet without a known session") - 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 == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_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] - -- 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 RTU 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.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(src_addr, rtu_advert, firmware_v) - - println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + 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 - else - log.debug("invalid establish packet (on RTU channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) - end - else - log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) - end - elseif r_chan == config.CRD_Channel then - -- look for an associated session - local session = svsessions.find_crd_session(src_addr) - if 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 == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] + _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(src_addr, i_seq_num, reactor_id, firmware_v) - -- 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 coordinator 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.CRD then - -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_crd_session(src_addr, firmware_v) - - if s_id ~= false then - println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf }) - else + if plc_id == false then + -- reactor already has a PLC assigned if last_ack ~= ESTABLISH_ACK.COLLISION then - log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + 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, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug("CRD_ESTABLISH: establish packet length mismatch") + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) - end - elseif protocol == PROTOCOL.SCADA_CRDN then - ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) + log.debug("invalid establish packet (on PLC channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) - end - elseif r_chan == config.PKT_Channel then - -- look for an associated session - local session = svsessions.find_pdg_session(src_addr) - - if 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 == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_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 PDG 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.PKT then - -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_pdg_session(src_addr, firmware_v) - - println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug("PDG_ESTABLISH: establish packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr)) - end - elseif protocol == PROTOCOL.SCADA_CRDN then - ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) - end - else - log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) + -- any other packet should be session related, discard it + log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) end else - log.debug("received packet for unknown channel " .. r_chan, true) + log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) end + elseif r_chan == config.RTU_Channel then + -- look for an associated session + local session = svsessions.find_rtu_session(src_addr) + + if protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame + -- MODBUS response + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug("discarding MODBUS_TCP packet without a known session") + 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 == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_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 RTU 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.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(src_addr, i_seq_num, rtu_advert, firmware_v) + + println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug("RTU_ESTABLISH: packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("invalid establish packet (on RTU channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) + end + elseif r_chan == config.CRD_Channel then + -- look for an associated session + local session = svsessions.find_crd_session(src_addr) + + if 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 == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_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 coordinator 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.CRD then + -- this is an attempt to establish a new coordinator session + local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v) + + if s_id ~= false then + println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf }) + else + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("CRD_ESTABLISH: establish packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) + end + elseif protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame + -- coordinator packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) + end + elseif r_chan == config.PKT_Channel then + -- look for an associated session + local session = svsessions.find_pdg_session(src_addr) + + if 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 == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_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 PDG 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.PKT then + -- this is an attempt to establish a new pocket diagnostic session + local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v) + + println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("PDG_ESTABLISH: establish packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr)) + end + elseif protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame + -- coordinator packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) + end + else + log.debug("received packet for unknown channel " .. r_chan, true) end end