PROTOCOLS = { MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol RPLC = 1, -- reactor PLC protocol SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc COORD_DATA = 3, -- data/control packets for coordinators to/from supervisory controllers COORD_API = 4 -- data/control packets for pocket computers to/from coordinators } RPLC_TYPES = { KEEP_ALIVE = 0, -- keep alive packets LINK_REQ = 1, -- linking requests STATUS = 2, -- reactor/system status MEK_STRUCT = 3, -- mekanism build structure MEK_SCRAM = 4, -- SCRAM reactor MEK_ENABLE = 5, -- enable reactor MEK_BURN_RATE = 6, -- set burn rate ISS_STATUS = 7, -- ISS status ISS_ALARM = 8, -- ISS alarm broadcast ISS_CLEAR = 9 -- clear ISS trip (if in bad state, will trip immediately) } RPLC_LINKING = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2 -- link denied due to existing active link } SCADA_MGMT_TYPES = { PING = 0, -- generic ping SV_HEARTBEAT = 1, -- supervisor heartbeat REMOTE_LINKED = 2, -- remote device linked RTU_ADVERT = 3, -- RTU capability advertisement RTU_HEARTBEAT = 4, -- RTU heartbeat } RTU_ADVERT_TYPES = { BOILER = 0, -- boiler TURBINE = 1, -- turbine IMATRIX = 2, -- induction matrix REDSTONE = 3 -- redstone I/O } -- generic SCADA packet object function scada_packet() local self = { modem_msg_in = nil, valid = false, raw = nil, seq_num = nil, protocol = nil, length = nil, payload = nil } -- make a SCADA packet local make = function (seq_num, protocol, payload) self.valid = true self.seq_num = seq_num self.protocol = protocol self.length = #payload self.payload = payload self.raw = { self.seq_num, self.protocol, self.payload } end -- parse in a modem message as a SCADA packet local receive = function (side, sender, reply_to, message, distance) self.modem_msg_in = { iface = side, s_port = sender, r_port = reply_to, msg = message, dist = distance } self.raw = self.modem_msg_in.msg if type(self.raw) == "table" then if #self.raw >= 3 then self.valid = true self.seq_num = self.raw[1] self.protocol = self.raw[2] self.length = #self.raw[3] self.payload = self.raw[3] end end return self.valid end -- public accessors -- local modem_event = function () return self.modem_msg_in end local raw_sendable = function () return self.raw end local local_port = function () return self.modem_msg_in.s_port end local remote_port = function () return self.modem_msg_in.r_port end local is_valid = function () return self.valid end local seq_num = function () return self.seq_num end local protocol = function () return self.protocol end local length = function () return self.length end local data = function () return self.payload end return { -- construct make = make, receive = receive, -- raw access modem_event = modem_event, raw_sendable = raw_sendable, -- ports local_port = local_port, remote_port = remote_port, -- well-formed is_valid = is_valid, -- packet properties seq_num = seq_num, protocol = protocol, length = length, data = data } end -- MODBUS packet -- modeled after MODBUS TCP packet function modbus_packet() local self = { frame = nil, raw = nil, txn_id = txn_id, length = length, unit_id = unit_id, func_code = func_code, data = data } -- make a MODBUS packet local make = function (txn_id, unit_id, func_code, data) self.txn_id = txn_id self.length = #data self.unit_id = unit_id self.func_code = func_code self.data = data -- populate raw array self.raw = { self.txn_id, self.unit_id, self.func_code } for i = 1, self.length do table.insert(self.raw, data[i]) end end -- decode a MODBUS packet from a SCADA frame local decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.MODBUS_TCP then local size_ok = frame.length() >= 3 if size_ok then local data = frame.data() make(data[1], data[2], data[3], { table.unpack(data, 4, #data) }) end return size_ok else log._debug("attempted MODBUS_TCP 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 local raw_sendable = function () return self.raw end -- get this packet local get = function () return { scada_frame = self.frame, txn_id = self.txn_id, length = self.length, unit_id = self.unit_id, func_code = self.func_code, data = self.data } end return { -- construct make = make, decode = decode, -- raw access raw_sendable = raw_sendable, -- formatted access get = get } end -- reactor PLC packet function rplc_packet() local self = { frame = nil, raw = nil, id = nil, type = nil, length = nil, body = nil } -- check that type is known local _rplc_type_valid = function () return self.type == RPLC_TYPES.KEEP_ALIVE or self.type == RPLC_TYPES.LINK_REQ or self.type == RPLC_TYPES.STATUS or self.type == RPLC_TYPES.MEK_STRUCT or self.type == RPLC_TYPES.MEK_SCRAM or self.type == RPLC_TYPES.MEK_ENABLE or self.type == RPLC_TYPES.MEK_BURN_RATE or self.type == RPLC_TYPES.ISS_ALARM or self.type == RPLC_TYPES.ISS_STATUS or self.type == RPLC_TYPES.ISS_CLEAR end -- make an RPLC packet local make = function (id, packet_type, data) -- packet accessor properties self.id = id self.type = packet_type self.length = #data self.data = data -- populate raw array self.raw = { self.id, self.type } for i = 1, #data do table.insert(self.raw, data[i]) end end -- decode an RPLC packet from a SCADA frame local decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.RPLC then local ok = frame.length() >= 2 if ok then local data = frame.data() make(data[1], data[2], { table.unpack(data, 3, #data) }) ok = _rplc_type_valid() end return ok else log._debug("attempted RPLC 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 local raw_sendable = function () return self.raw end -- get this packet local get = function () return { scada_frame = self.frame, id = self.id, type = self.type, length = self.length, data = self.data } end return { -- construct make = make, decode = decode, -- raw access raw_sendable = raw_sendable, -- formatted access get = get } end -- SCADA management packet function mgmt_packet() local self = { frame = nil, raw = nil, type = nil, length = nil, data = nil } -- check that type is known local _scada_type_valid = function () return self.type == SCADA_MGMT_TYPES.PING or self.type == SCADA_MGMT_TYPES.SV_HEARTBEAT or self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or self.type == SCADA_MGMT_TYPES.RTU_ADVERT or self.type == SCADA_MGMT_TYPES.RTU_HEARTBEAT end -- make a SCADA management packet local make = function (packet_type, data) -- 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 table.insert(self.raw, data[i]) end end -- decode a SCADA management packet from a SCADA frame local decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.SCADA_MGMT then local ok = #data >= 1 if ok then local data = frame.data() make(data[1], { table.unpack(data, 2, #data) }) ok = _scada_type_valid() end return ok else log._debug("attempted SCADA_MGMT 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 local raw_sendable = function () return self.raw end -- get this packet local get = function () return { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } end return { -- construct make = make, decode = decode, -- raw access raw_sendable = raw_sendable, -- formatted access get = get } end -- SCADA coordinator packet -- @todo function coord_packet() local self = { frame = nil, raw = nil, type = nil, length = nil, data = nil } local _coord_type_valid = function () -- @todo return false end -- make a coordinator packet local make = function (packet_type, data) -- 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 table.insert(self.raw, data[i]) end end -- decode a coordinator packet from a SCADA frame local decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.COORD_DATA then local ok = #data >= 1 if ok then local data = frame.data() make(data[1], { table.unpack(data, 2, #data) }) ok = _coord_type_valid() end return ok else log._debug("attempted COORD_DATA 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 local raw_sendable = function () return self.raw end -- get this packet local get = function () return { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } end return { -- construct make = make, decode = decode, -- raw access raw_sendable = raw_sendable, -- formatted access get = get } end -- coordinator API (CAPI) packet -- @todo function capi_packet() local self = { frame = nil, raw = nil, type = nil, length = nil, data = nil } local _coord_type_valid = function () -- @todo return false end -- make a coordinator packet local make = function (packet_type, data) -- 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 table.insert(self.raw, data[i]) end end -- decode a coordinator packet from a SCADA frame local decode = function (frame) if frame then self.frame = frame if frame.protocol() == PROTOCOLS.COORD_API then local ok = #data >= 1 if ok then local data = frame.data() make(data[1], { table.unpack(data, 2, #data) }) ok = _coord_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 local raw_sendable = function () return self.raw end -- get this packet local get = function () return { scada_frame = self.frame, type = self.type, length = self.length, data = self.data } end return { -- construct make = make, decode = decode, -- raw access raw_sendable = raw_sendable, -- formatted access get = get } end