#300 comms cleanup

This commit is contained in:
Mikayla
2023-08-30 20:45:48 +00:00
parent f7766d8cba
commit cfc6479dd5
17 changed files with 194 additions and 345 deletions

View File

@@ -6,23 +6,25 @@ local log = require("scada-common.log")
local insert = table.insert
---@type integer computer ID
---@diagnostic disable-next-line: undefined-field
local COMPUTER_ID = os.getComputerID() ---@type integer computer ID
local COMPUTER_ID = os.getComputerID()
local max_distance = nil ---@type number|nil maximum acceptable transmission distance
---@type number|nil maximum acceptable transmission distance
local max_distance = nil
---@class comms
local comms = {}
comms.version = "2.2.1"
-- protocol version (non-protocol changes tracked by util.lua version)
comms.version = "2.3.0"
---@enum PROTOCOL
local PROTOCOL = {
MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol
RPLC = 1, -- reactor PLC protocol
SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc
SCADA_CRDN = 3, -- data/control packets for coordinators to/from supervisory controllers
COORD_API = 4 -- data/control packets for pocket computers to/from coordinators
SCADA_CRDN = 3 -- data/control packets for coordinators to/from supervisory controllers
}
---@enum RPLC_TYPE
@@ -40,8 +42,8 @@ local RPLC_TYPE = {
AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited
}
---@enum SCADA_MGMT_TYPE
local SCADA_MGMT_TYPE = {
---@enum MGMT_TYPE
local MGMT_TYPE = {
ESTABLISH = 0, -- establish new connection
KEEP_ALIVE = 1, -- keep alive packet w/ RTT
CLOSE = 2, -- close a connection
@@ -53,8 +55,8 @@ local SCADA_MGMT_TYPE = {
DIAG_ALARM_SET = 8 -- diagnostic: set alarm to simulate audio for
}
---@enum SCADA_CRDN_TYPE
local SCADA_CRDN_TYPE = {
---@enum CRDN_TYPE
local CRDN_TYPE = {
INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator
FAC_BUILDS = 1, -- facility RTU builds
FAC_STATUS = 2, -- state of facility and facility devices
@@ -64,10 +66,6 @@ local SCADA_CRDN_TYPE = {
UNIT_CMD = 6 -- command a reactor unit
}
---@enum CAPI_TYPE
local CAPI_TYPE = {
}
---@enum ESTABLISH_ACK
local ESTABLISH_ACK = {
ALLOW = 0, -- link approved
@@ -119,9 +117,8 @@ local UNIT_COMMAND = {
comms.PROTOCOL = PROTOCOL
comms.RPLC_TYPE = RPLC_TYPE
comms.SCADA_MGMT_TYPE = SCADA_MGMT_TYPE
comms.SCADA_CRDN_TYPE = SCADA_CRDN_TYPE
comms.CAPI_TYPE = CAPI_TYPE
comms.MGMT_TYPE = MGMT_TYPE
comms.CRDN_TYPE = CRDN_TYPE
comms.ESTABLISH_ACK = ESTABLISH_ACK
comms.DEVICE_TYPE = DEVICE_TYPE
@@ -134,8 +131,8 @@ comms.FAC_COMMAND = FAC_COMMAND
-- destination broadcast address (to all devices)
comms.BROADCAST = -1
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame
-- configure the maximum allowable message receive distance<br>
-- packets received with distances greater than this will be silently discarded
@@ -144,7 +141,7 @@ function comms.set_trusted_range(distance)
if distance == 0 then max_distance = nil else max_distance = distance end
end
-- generic SCADA packet object
-- generic SCADA packet
---@nodiscard
function comms.scada_packet()
local self = {
@@ -199,9 +196,9 @@ function comms.scada_packet()
self.valid = false
self.raw = self.modem_msg_in.msg
if (type(max_distance) == "number") and (distance > max_distance) then
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
-- outside of maximum allowable transmission distance
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range")
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
else
if type(self.raw) == "table" then
if #self.raw == 5 then
@@ -227,12 +224,8 @@ function comms.scada_packet()
-- 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.seq_num) == "number" and
type(self.protocol) == "number" and
type(self.payload) == "table"
self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and
type(self.seq_num) == "number" and type(self.protocol) == "number" and type(self.payload) == "table"
end
end
@@ -275,7 +268,7 @@ function comms.scada_packet()
return public
end
-- authenticated SCADA packet object
-- authenticated SCADA packet
---@nodiscard
function comms.authd_packet()
local self = {
@@ -325,7 +318,7 @@ function comms.authd_packet()
if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then
-- outside of maximum allowable transmission distance
-- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range")
-- log.debug("comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)")
else
if type(self.raw) == "table" then
if #self.raw == 4 then
@@ -343,11 +336,8 @@ function comms.authd_packet()
-- 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"
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"
end
end
@@ -381,8 +371,7 @@ function comms.authd_packet()
return public
end
-- MODBUS packet<br>
-- modeled after MODBUS TCP packet
-- MODBUS packet, modeled after MODBUS TCP
---@nodiscard
function comms.modbus_packet()
local self = {
@@ -436,9 +425,7 @@ function comms.modbus_packet()
public.make(data[1], data[2], data[3], { table.unpack(data, 4, #data) })
end
local valid = type(self.txn_id) == "number" and
type(self.unit_id) == "number" and
type(self.func_code) == "number"
local valid = type(self.txn_id) == "number" and type(self.unit_id) == "number" and type(self.func_code) == "number"
return size_ok and valid
else
@@ -489,21 +476,6 @@ function comms.rplc_packet()
---@class rplc_packet
local public = {}
-- check that type is known
local function _rplc_type_valid()
return self.type == RPLC_TYPE.STATUS or
self.type == RPLC_TYPE.MEK_STRUCT or
self.type == RPLC_TYPE.MEK_BURN_RATE or
self.type == RPLC_TYPE.RPS_ENABLE or
self.type == RPLC_TYPE.RPS_SCRAM or
self.type == RPLC_TYPE.RPS_ASCRAM or
self.type == RPLC_TYPE.RPS_STATUS or
self.type == RPLC_TYPE.RPS_ALARM or
self.type == RPLC_TYPE.RPS_RESET or
self.type == RPLC_TYPE.RPS_AUTO_RESET or
self.type == RPLC_TYPE.AUTO_BURN_RATE
end
-- make an RPLC packet
---@param id integer
---@param packet_type RPLC_TYPE
@@ -539,7 +511,6 @@ function comms.rplc_packet()
if ok then
local data = frame.data()
public.make(data[1], data[2], { table.unpack(data, 3, #data) })
ok = _rplc_type_valid()
end
ok = ok and type(self.id) == "number"
@@ -583,7 +554,7 @@ function comms.mgmt_packet()
local self = {
frame = nil,
raw = {},
type = 0, ---@type SCADA_MGMT_TYPE
type = 0, ---@type MGMT_TYPE
length = 0,
data = {}
}
@@ -591,22 +562,8 @@ function comms.mgmt_packet()
---@class mgmt_packet
local public = {}
-- check that type is known
local function _scada_type_valid()
return self.type == SCADA_MGMT_TYPE.ESTABLISH or
self.type == SCADA_MGMT_TYPE.KEEP_ALIVE or
self.type == SCADA_MGMT_TYPE.CLOSE or
self.type == SCADA_MGMT_TYPE.REMOTE_LINKED or
self.type == SCADA_MGMT_TYPE.RTU_ADVERT or
self.type == SCADA_MGMT_TYPE.RTU_DEV_REMOUNT or
self.type == SCADA_MGMT_TYPE.RTU_TONE_ALARM or
self.type == SCADA_MGMT_TYPE.DIAG_TONE_GET or
self.type == SCADA_MGMT_TYPE.DIAG_TONE_SET or
self.type == SCADA_MGMT_TYPE.DIAG_ALARM_SET
end
-- make a SCADA management packet
---@param packet_type SCADA_MGMT_TYPE
---@param packet_type MGMT_TYPE
---@param data table
function public.make(packet_type, data)
if type(data) == "table" then
@@ -638,7 +595,6 @@ function comms.mgmt_packet()
if ok then
local data = frame.data()
public.make(data[1], { table.unpack(data, 2, #data) })
ok = _scada_type_valid()
end
return ok
@@ -679,7 +635,7 @@ function comms.crdn_packet()
local self = {
frame = nil,
raw = {},
type = 0, ---@type SCADA_CRDN_TYPE
type = 0, ---@type CRDN_TYPE
length = 0,
data = {}
}
@@ -687,20 +643,8 @@ function comms.crdn_packet()
---@class crdn_packet
local public = {}
-- check that type is known
---@nodiscard
local function _crdn_type_valid()
return self.type == SCADA_CRDN_TYPE.INITIAL_BUILDS or
self.type == SCADA_CRDN_TYPE.FAC_BUILDS or
self.type == SCADA_CRDN_TYPE.FAC_STATUS or
self.type == SCADA_CRDN_TYPE.FAC_CMD or
self.type == SCADA_CRDN_TYPE.UNIT_BUILDS or
self.type == SCADA_CRDN_TYPE.UNIT_STATUSES or
self.type == SCADA_CRDN_TYPE.UNIT_CMD
end
-- make a coordinator packet
---@param packet_type SCADA_CRDN_TYPE
---@param packet_type CRDN_TYPE
---@param data table
function public.make(packet_type, data)
if type(data) == "table" then
@@ -732,7 +676,6 @@ function comms.crdn_packet()
if ok then
local data = frame.data()
public.make(data[1], { table.unpack(data, 2, #data) })
ok = _crdn_type_valid()
end
return ok
@@ -767,92 +710,4 @@ function comms.crdn_packet()
return public
end
-- coordinator API (CAPI) packet
---@todo implement for pocket access, set enum type for self.type
---@nodiscard
function comms.capi_packet()
local self = {
frame = nil,
raw = {},
type = 0,
length = 0,
data = {}
}
---@class capi_packet
local public = {}
local function _capi_type_valid()
---@todo
return false
end
-- make a coordinator API packet
---@param packet_type CAPI_TYPE
---@param data table
function public.make(packet_type, data)
if type(data) == "table" then
-- packet accessor properties
self.type = packet_type
self.length = #data
self.data = data
-- populate raw array
self.raw = { self.type }
for i = 1, #data do
insert(self.raw, data[i])
end
else
log.error("comms.capi_packet.make(): data not table")
end
end
-- decode a coordinator API packet from a SCADA frame
---@param frame scada_packet
---@return boolean success
function public.decode(frame)
if frame then
self.frame = frame
if frame.protocol() == PROTOCOL.COORD_API then
local ok = frame.length() >= 1
if ok then
local data = frame.data()
public.make(data[1], { table.unpack(data, 2, #data) })
ok = _capi_type_valid()
end
return ok
else
log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("nil frame encountered", true)
return false
end
end
-- get raw to send
---@nodiscard
function public.raw_sendable() return self.raw end
-- get this packet as a frame with an immutable relation to this object
---@nodiscard
function public.get()
---@class capi_frame
local frame = {
scada_frame = self.frame,
type = self.type,
length = self.length,
data = self.data
}
return frame
end
return public
end
return comms