#300 comms cleanup
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user