#488 HMAC acceleration and seq_num changes

This commit is contained in:
Mikayla 2024-06-29 02:27:55 +00:00
parent 2bc20ec312
commit d2bc4f6bc0
19 changed files with 355 additions and 385 deletions

View File

@ -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)

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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