From b628472d8145106fdaf9683e4e65c35b5d1f9822 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 15 Jun 2022 15:35:34 -0400 Subject: [PATCH] #74 work on coordinator comms --- coordinator/coordinator.lua | 166 +++++++++++++++++++++++++++++++++++- scada-common/comms.lua | 18 +++- 2 files changed, 179 insertions(+), 5 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 76c9824..5dbb39e 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -6,13 +6,19 @@ local util = require("scada-common.util") local dialog = require("coordinator.util.dialog") +local coordinator = {} + local print = util.print local println = util.println local print_ts = util.print_ts local println_ts = util.println_ts -local coordinator = {} +local PROTOCOLS = comms.PROTOCOLS +local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES +local COORD_TYPES = comms.COORD_TYPES +-- request the user to select a monitor +---@param names table available monitors local function ask_monitor(names) println("available monitors:") for i = 1, #names do @@ -30,6 +36,8 @@ local function ask_monitor(names) return iface end +-- configure monitor layout +---@param num_units integer number of units expected function coordinator.configure_monitors(num_units) ---@class monitors_struct local monitors = { @@ -135,10 +143,162 @@ function coordinator.configure_monitors(num_units) end -- coordinator communications -function coordinator.coord_comms() +---@param conn_watchdog watchdog +function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_listen, api_listen, conn_watchdog) local self = { - reactor_struct_cache = nil + seq_num = 0, + r_seq_num = nil, + modem = modem, + connected = false } + + ---@class coord_comms + local public = {} + + -- PRIVATE FUNCTIONS -- + + -- open all channels + local function _open_channels() + if not self.modem.isOpen(sv_listen) then + self.modem.open(sv_listen) + end + + if not self.modem.isOpen(api_listen) then + self.modem.open(api_listen) + end + end + + -- open at construct time + _open_channels() + + -- send a coordinator packet + ---@param msg_type COORD_TYPES + ---@param msg string + local function _send(msg_type, msg) + local s_pkt = comms.scada_packet() + local c_pkt = comms.coord_packet() + + c_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.COORD_DATA, c_pkt.raw_sendable()) + + self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg string + local function _send_mgmt(msg_type, msg) + local s_pkt = comms.scada_packet() + local m_pkt = comms.mgmt_packet() + + m_pkt.make(msg_type, msg) + s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable()) + + self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable()) + self.seq_num = self.seq_num + 1 + end + + -- PUBLIC FUNCTIONS -- + + -- reconnect a newly connected modem + ---@param modem table +---@diagnostic disable-next-line: redefined-local + function public.reconnect_modem(modem) + self.modem = modem + _open_channels() + end + + -- parse a packet + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return mgmt_frame|coord_frame|capi_frame|nil packet + function public.parse_packet(side, sender, reply_to, message, distance) + local pkt = nil + local s_pkt = comms.scada_packet() + + -- parse packet as generic SCADA packet + s_pkt.receive(side, sender, reply_to, message, distance) + + if s_pkt.is_valid() then + -- get as SCADA management packet + if s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then + local mgmt_pkt = comms.mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + pkt = mgmt_pkt.get() + end + -- get as coordinator packet + elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then + local coord_pkt = comms.coord_packet() + if coord_pkt.decode(s_pkt) then + pkt = coord_pkt.get() + end + -- get as coordinator API packet + elseif s_pkt.protocol() == PROTOCOLS.COORD_API then + local capi_pkt = comms.capi_packet() + if capi_pkt.decode(s_pkt) then + pkt = capi_pkt.get() + end + else + log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + end + end + + return pkt + end + + -- handle a packet + ---@param packet mgmt_frame|coord_frame|capi_frame + function public.handle_packet(packet) + if packet ~= nil then + -- check sequence number + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + elseif self.connected and self.r_seq_num >= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + return + else + self.r_seq_num = packet.scada_frame.seq_num() + end + + -- feed watchdog on valid sequence number + conn_watchdog.feed() + + local protocol = packet.scada_frame.protocol() + + -- handle packet + if protocol == PROTOCOLS.COORD_DATA then + if packet.type == COORD_TYPES.ESTABLISH then + elseif packet.type == COORD_TYPES.QUERY_UNIT then + elseif packet.type == COORD_TYPES.QUERY_FACILITY then + elseif packet.type == COORD_TYPES.COMMAND_UNIT then + elseif packet.type == COORD_TYPES.ALARM then + else + log.warning("received unknown coordinator data packet type " .. packet.type) + end + elseif protocol == PROTOCOLS.COORD_API then + elseif protocol == PROTOCOLS.SCADA_MGMT then + if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then + -- keep alive response received + elseif packet.type == SCADA_MGMT_TYPES.CLOSE then + -- handle session close + conn_watchdog.cancel() + println_ts("server connection closed by remote host") + log.warning("server connection closed by remote host") + else + log.warning("received unknown SCADA_MGMT packet type " .. packet.type) + end + else + -- should be unreachable assuming packet is from parse_packet() + log.error("illegal packet type " .. protocol, true) + end + end + end + + return public end return coordinator diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 8422bd4..e834d1e 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -48,6 +48,15 @@ local SCADA_MGMT_TYPES = { REMOTE_LINKED = 3 -- remote device linked } +---@alias COORD_TYPES integer +local COORD_TYPES = { + ESTABLISH = 0, -- initial greeting + QUERY_UNIT = 1, -- query the state of a unit + QUERY_FACILITY = 2, -- query general facility status + COMMAND_UNIT = 3, -- command a reactor unit + ALARM = 4 -- alarm signaling +} + ---@alias RTU_UNIT_TYPES integer local RTU_UNIT_TYPES = { REDSTONE = 0, -- redstone I/O @@ -66,6 +75,7 @@ comms.PROTOCOLS = PROTOCOLS comms.RPLC_TYPES = RPLC_TYPES comms.RPLC_LINKING = RPLC_LINKING comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES +comms.COORD_TYPES = COORD_TYPES comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES -- generic SCADA packet object @@ -436,9 +446,13 @@ function comms.coord_packet() ---@class coord_packet local public = {} + -- check that type is known local function _coord_type_valid() - -- @todo - return false + return self.type == COORD_TYPES.ESTABLISH or + self.type == COORD_TYPES.QUERY_UNIT or + self.type == COORD_TYPES.QUERY_FACILITY or + self.type == COORD_TYPES.COMMAND_UNIT or + self.type == COORD_TYPES.ALARM end -- make a coordinator packet