mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#51 WIP network interface controller
This commit is contained in:
parent
282c7db3eb
commit
1cdf66a8c3
@ -212,13 +212,13 @@ end
|
||||
-- coordinator communications
|
||||
---@nodiscard
|
||||
---@param version string coordinator version
|
||||
---@param modem table modem device
|
||||
---@param nic nic network interface device
|
||||
---@param crd_channel integer port of configured supervisor
|
||||
---@param svr_channel integer listening port for supervisor replys
|
||||
---@param pkt_channel integer listening port for pocket API
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel, range, sv_watchdog)
|
||||
function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, range, sv_watchdog)
|
||||
local self = {
|
||||
sv_linked = false,
|
||||
sv_addr = comms.BROADCAST,
|
||||
@ -234,16 +234,12 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(crd_channel)
|
||||
end
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(crd_channel)
|
||||
|
||||
_conf_channels()
|
||||
|
||||
-- link modem to apisessions
|
||||
apisessions.init(modem)
|
||||
-- link nic to apisessions
|
||||
apisessions.init(nic)
|
||||
|
||||
-- send a packet to the supervisor
|
||||
---@param msg_type SCADA_MGMT_TYPE|SCADA_CRDN_TYPE
|
||||
@ -263,7 +259,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_addr, self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, crd_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, crd_channel, s_pkt)
|
||||
self.sv_seq_num = self.sv_seq_num + 1
|
||||
end
|
||||
|
||||
@ -277,7 +273,7 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, { ack })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(pkt_channel, crd_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(pkt_channel, crd_channel, s_pkt)
|
||||
self.last_api_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
@ -297,14 +293,6 @@ function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel
|
||||
---@class coord_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
apisessions.relink_modem(new_modem)
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
function public.close()
|
||||
sv_watchdog.cancel()
|
||||
|
@ -10,7 +10,7 @@ local pocket = require("coordinator.session.pocket")
|
||||
local apisessions = {}
|
||||
|
||||
local self = {
|
||||
modem = nil,
|
||||
nic = nil,
|
||||
next_id = 0,
|
||||
sessions = {}
|
||||
}
|
||||
@ -31,7 +31,7 @@ local function _api_handle_outq(session)
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.PACKET then
|
||||
-- handle a packet to be sent
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
self.nic.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message)
|
||||
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- handle instruction/notification
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
@ -58,7 +58,7 @@ local function _shutdown(session)
|
||||
while session.out_queue.ready() do
|
||||
local msg = session.out_queue.pop()
|
||||
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
self.nic.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message)
|
||||
end
|
||||
end
|
||||
|
||||
@ -68,15 +68,9 @@ end
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
-- initialize apisessions
|
||||
---@param modem table
|
||||
function apisessions.init(modem)
|
||||
self.modem = modem
|
||||
end
|
||||
|
||||
-- re-link the modem
|
||||
---@param modem table
|
||||
function apisessions.relink_modem(modem)
|
||||
self.modem = modem
|
||||
---@param nic nic
|
||||
function apisessions.init(nic)
|
||||
self.nic = nic
|
||||
end
|
||||
|
||||
-- find a session by remote port
|
||||
|
@ -6,6 +6,7 @@ require("/initenv").init_env()
|
||||
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
@ -20,7 +21,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.16.1"
|
||||
local COORDINATOR_VERSION = "v0.17.0"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -147,8 +148,9 @@ local function main()
|
||||
conn_watchdog.cancel()
|
||||
log.debug("startup> conn watchdog created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.CRD_CHANNEL, config.SVR_CHANNEL,
|
||||
-- init network interface then start comms
|
||||
local nic = network.nic(modem)
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, config.CRD_CHANNEL, config.SVR_CHANNEL,
|
||||
config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
@ -218,8 +220,6 @@ local function main()
|
||||
|
||||
local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
|
||||
|
||||
local no_modem = false
|
||||
|
||||
if ui_ok then
|
||||
-- start connection watchdog
|
||||
conn_watchdog.feed()
|
||||
@ -239,8 +239,9 @@ local function main()
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
-- we only really care if this is our wireless modem
|
||||
if device == modem then
|
||||
no_modem = true
|
||||
-- if it is another modem, handle other peripheral losses separately
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
log_sys("comms modem disconnected")
|
||||
println_ts("wireless modem disconnected!")
|
||||
|
||||
@ -254,6 +255,7 @@ local function main()
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
if renderer.is_monitor_used(device) then
|
||||
---@todo will be handled properly in #249
|
||||
-- "halt and catch fire" style handling
|
||||
local msg = "lost a configured monitor, system will now exit"
|
||||
println_ts(msg)
|
||||
@ -275,9 +277,7 @@ local function main()
|
||||
if type == "modem" then
|
||||
if device.isWireless() then
|
||||
-- reconnected modem
|
||||
no_modem = false
|
||||
modem = device
|
||||
coord_comms.reconnect_modem(modem)
|
||||
nic.connect(device)
|
||||
|
||||
log_sys("comms modem reconnected")
|
||||
println_ts("wireless modem reconnected.")
|
||||
@ -289,6 +289,7 @@ local function main()
|
||||
log_sys("wired modem reconnected")
|
||||
end
|
||||
-- elseif type == "monitor" then
|
||||
---@todo will be handled properly in #249
|
||||
-- not supported, system will exit on loss of in-use monitors
|
||||
elseif type == "speaker" then
|
||||
local msg = "alarm sounder speaker reconnected"
|
||||
@ -322,7 +323,7 @@ local function main()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
|
||||
if not no_modem then
|
||||
if nic.connected() then
|
||||
-- try to re-connect to the supervisor
|
||||
if not init_connect_sv() then break end
|
||||
ui_ok = init_start_ui()
|
||||
@ -350,7 +351,7 @@ local function main()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
|
||||
if not no_modem then
|
||||
if nic.connected() then
|
||||
-- try to re-connect to the supervisor
|
||||
if not init_connect_sv() then break end
|
||||
ui_ok = init_start_ui()
|
||||
|
@ -1,5 +1,8 @@
|
||||
local Lockbox = {};
|
||||
|
||||
-- cc-mek-scada lockbox version
|
||||
Lockbox.VERSION = "1.0"
|
||||
|
||||
--[[
|
||||
package.path = "./?.lua;"
|
||||
.. "./cipher/?.lua;"
|
||||
|
@ -17,14 +17,14 @@ local pocket = {}
|
||||
-- pocket coordinator + supervisor communications
|
||||
---@nodiscard
|
||||
---@param version string pocket version
|
||||
---@param modem table modem device
|
||||
---@param nic nic network interface device
|
||||
---@param pkt_channel integer pocket comms channel
|
||||
---@param svr_channel integer supervisor access channel
|
||||
---@param crd_channel integer coordinator access channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
---@param api_watchdog watchdog
|
||||
function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog)
|
||||
function pocket.comms(version, nic, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog)
|
||||
local self = {
|
||||
sv = {
|
||||
linked = false,
|
||||
@ -47,13 +47,9 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(pkt_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(pkt_channel)
|
||||
|
||||
-- send a management packet to the supervisor
|
||||
---@param msg_type SCADA_MGMT_TYPE
|
||||
@ -65,7 +61,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv.addr, self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(svr_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(svr_channel, pkt_channel, s_pkt)
|
||||
self.sv.seq_num = self.sv.seq_num + 1
|
||||
end
|
||||
|
||||
@ -79,7 +75,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
nic.transmit(crd_channel, pkt_channel, s_pkt)
|
||||
self.api.seq_num = self.api.seq_num + 1
|
||||
end
|
||||
|
||||
@ -93,7 +89,7 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
-- pkt.make(msg_type, msg)
|
||||
-- s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
|
||||
-- modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
-- nic.transmit(crd_channel, pkt_channel, s_pkt)
|
||||
-- self.api.seq_num = self.api.seq_num + 1
|
||||
-- end
|
||||
|
||||
@ -124,13 +120,6 @@ function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, ran
|
||||
---@class pocket_comms
|
||||
local public = {}
|
||||
|
||||
-- reconnect a newly connected modem
|
||||
---@param new_modem table
|
||||
function public.reconnect_modem(new_modem)
|
||||
modem = new_modem
|
||||
_conf_channels()
|
||||
end
|
||||
|
||||
-- close connection to the supervisor
|
||||
function public.close_sv()
|
||||
sv_watchdog.cancel()
|
||||
|
@ -6,6 +6,7 @@ require("/initenv").init_env()
|
||||
|
||||
local crash = require("scada-common.crash")
|
||||
local log = require("scada-common.log")
|
||||
local network = require("scada-common.network")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
@ -17,7 +18,7 @@ local coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.4.5"
|
||||
local POCKET_VERSION = "alpha-v0.5.0"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -88,8 +89,9 @@ local function main()
|
||||
|
||||
log.debug("startup> conn watchdogs created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.PKT_CHANNEL, config.SVR_CHANNEL,
|
||||
-- init network interface then start comms
|
||||
local nic = network.nic(modem)
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, nic, config.PKT_CHANNEL, config.SVR_CHANNEL,
|
||||
config.CRD_CHANNEL, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api)
|
||||
log.debug("startup> comms init")
|
||||
|
||||
|
@ -205,7 +205,7 @@ function comms.scada_packet()
|
||||
self.protocol = self.raw[4]
|
||||
self.mac = self.raw[5]
|
||||
|
||||
-- element 5 must be a table
|
||||
-- element 6 must be a table
|
||||
if type(self.raw[6]) == "table" then
|
||||
self.length = #self.raw[6]
|
||||
self.payload = self.raw[6]
|
||||
|
@ -1,152 +0,0 @@
|
||||
--
|
||||
-- Cryptographic Communications Engine
|
||||
--
|
||||
|
||||
local md5 = require("lockbox.digest.md5")
|
||||
local sha2_256 = require("lockbox.digest.sha2_256")
|
||||
local pbkdf2 = require("lockbox.kdf.pbkdf2")
|
||||
local hmac = require("lockbox.mac.hmac")
|
||||
local stream = require("lockbox.util.stream")
|
||||
local array = require("lockbox.util.array")
|
||||
local comms = require("scada-common.comms")
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local crypto = {}
|
||||
|
||||
local c_eng = {
|
||||
key = nil,
|
||||
hmac = nil
|
||||
}
|
||||
|
||||
-- initialize cryptographic system
|
||||
function crypto.init(password)
|
||||
local key_deriv = pbkdf2()
|
||||
|
||||
-- setup PBKDF2
|
||||
key_deriv.setPassword(password)
|
||||
key_deriv.setSalt("pepper")
|
||||
key_deriv.setIterations(32)
|
||||
key_deriv.setBlockLen(8)
|
||||
key_deriv.setDKeyLen(16)
|
||||
|
||||
local start = util.time_ms()
|
||||
|
||||
key_deriv.setPRF(hmac().setBlockSize(64).setDigest(sha2_256))
|
||||
key_deriv.finish()
|
||||
|
||||
local message = "pbkdf2 key derivation took " .. (util.time_ms() - start) .. "ms"
|
||||
log.dmesg(message, "CRYPTO", colors.yellow)
|
||||
log.info("crypto.init: " .. message)
|
||||
|
||||
c_eng.key = array.fromHex(key_deriv.asHex())
|
||||
|
||||
-- initialize HMAC
|
||||
c_eng.hmac = hmac()
|
||||
c_eng.hmac.setBlockSize(64)
|
||||
c_eng.hmac.setDigest(md5)
|
||||
c_eng.hmac.setKey(c_eng.key)
|
||||
|
||||
message = "init: completed in " .. (util.time_ms() - start) .. "ms"
|
||||
log.dmesg(message, "CRYPTO", colors.yellow)
|
||||
log.info("crypto." .. message)
|
||||
end
|
||||
|
||||
-- generate HMAC of message
|
||||
---@nodiscard
|
||||
---@param message string initial value concatenated with ciphertext
|
||||
function crypto.hmac(message)
|
||||
local start = util.time_ms()
|
||||
|
||||
c_eng.hmac.init()
|
||||
c_eng.hmac.update(stream.fromString(message))
|
||||
c_eng.hmac.finish()
|
||||
|
||||
local hash = c_eng.hmac.asHex()
|
||||
|
||||
log.debug("crypto.hmac: hmac-md5 took " .. (util.time_ms() - start) .. "ms")
|
||||
log.debug("crypto.hmac: hmac = " .. util.strval(hash))
|
||||
|
||||
return hash
|
||||
end
|
||||
|
||||
-- wrap a modem as a secure modem to send encrypted traffic
|
||||
---@param modem table modem to wrap
|
||||
function crypto.secure_modem(modem)
|
||||
---@class secure_modem
|
||||
---@field open function
|
||||
---@field isOpen function
|
||||
---@field close function
|
||||
---@field closeAll function
|
||||
---@field isWireless function
|
||||
---@field getNamesRemote function
|
||||
---@field isPresentRemote function
|
||||
---@field getTypeRemote function
|
||||
---@field hasTypeRemote function
|
||||
---@field getMethodsRemote function
|
||||
---@field callRemote function
|
||||
---@field getNameLocal function
|
||||
local public = {}
|
||||
|
||||
-- wrap a modem
|
||||
---@param reconnected_modem table
|
||||
function public.wrap(reconnected_modem)
|
||||
modem = reconnected_modem
|
||||
for key, func in pairs(modem) do
|
||||
public[key] = func
|
||||
end
|
||||
end
|
||||
|
||||
-- wrap modem functions, then we replace transmit
|
||||
public.wrap(modem)
|
||||
|
||||
-- send a packet with message authentication
|
||||
---@param packet scada_packet packet raw_sendable
|
||||
function public.transmit(packet)
|
||||
local start = util.time_ms()
|
||||
local message = textutils.serialize(packet.raw_verifiable(), { allow_repetitions = true, compact = true })
|
||||
local computed_hmac = crypto.hmac(message)
|
||||
|
||||
packet.set_mac(computed_hmac)
|
||||
|
||||
log.debug("crypto.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
||||
|
||||
modem.transmit(packet.remote_channel(), packet.local_channel(), packet.raw_sendable())
|
||||
end
|
||||
|
||||
-- parse in a modem message as a network packet
|
||||
---@nodiscard
|
||||
---@param side string modem side
|
||||
---@param sender integer sender channel
|
||||
---@param reply_to integer reply channel
|
||||
---@param message any packet sent with message authentication
|
||||
---@param distance integer transmission distance
|
||||
---@return scada_packet|nil packet received packet if valid and passed authentication check
|
||||
function public.receive(side, sender, reply_to, message, distance)
|
||||
local packet = nil
|
||||
local s_packet = comms.scada_packet()
|
||||
|
||||
-- parse packet as generic SCADA packet
|
||||
s_packet.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_packet.is_valid() then
|
||||
local start = util.time_ms()
|
||||
local packet_hmac = s_packet.mac()
|
||||
local computed_hmac = crypto.hmac(textutils.serialize(s_packet.raw_verifiable(), { allow_repetitions = true, compact = true }))
|
||||
|
||||
if packet_hmac == computed_hmac then
|
||||
log.debug("crypto.secure_modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
||||
packet = s_packet
|
||||
else
|
||||
log.debug("crypto.secure_modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
||||
end
|
||||
end
|
||||
|
||||
return packet
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
return crypto
|
225
scada-common/network.lua
Normal file
225
scada-common/network.lua
Normal file
@ -0,0 +1,225 @@
|
||||
--
|
||||
-- Network Communications
|
||||
--
|
||||
|
||||
local md5 = require("lockbox.digest.md5")
|
||||
local sha256 = require("lockbox.digest.sha2_256")
|
||||
local pbkdf2 = require("lockbox.kdf.pbkdf2")
|
||||
local hmac = require("lockbox.mac.hmac")
|
||||
local stream = require("lockbox.util.stream")
|
||||
local array = require("lockbox.util.array")
|
||||
local comms = require("scada-common.comms")
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local network = {}
|
||||
|
||||
local c_eng = {
|
||||
key = nil,
|
||||
hmac = nil
|
||||
}
|
||||
|
||||
-- initialize message authentication system
|
||||
---@param passkey string facility passkey
|
||||
---@return integer init_time milliseconds init took
|
||||
function network.init_mac(passkey)
|
||||
local start = util.time_ms()
|
||||
|
||||
local key_deriv = pbkdf2()
|
||||
|
||||
-- setup PBKDF2
|
||||
key_deriv.setPassword(passkey)
|
||||
key_deriv.setSalt("pepper")
|
||||
key_deriv.setIterations(32)
|
||||
key_deriv.setBlockLen(8)
|
||||
key_deriv.setDKeyLen(16)
|
||||
key_deriv.setPRF(hmac().setBlockSize(64).setDigest(sha256))
|
||||
key_deriv.finish()
|
||||
|
||||
c_eng.key = array.fromHex(key_deriv.asHex())
|
||||
|
||||
-- initialize HMAC
|
||||
c_eng.hmac = hmac()
|
||||
c_eng.hmac.setBlockSize(64)
|
||||
c_eng.hmac.setDigest(md5)
|
||||
c_eng.hmac.setKey(c_eng.key)
|
||||
|
||||
local init_time = util.time_ms() - start
|
||||
log.info("network.init_mac completed in " .. init_time .. "ms")
|
||||
|
||||
return init_time
|
||||
end
|
||||
|
||||
-- generate HMAC of message
|
||||
---@nodiscard
|
||||
---@param message string initial value concatenated with ciphertext
|
||||
local function compute_hmac(message)
|
||||
local start = util.time_ms()
|
||||
|
||||
c_eng.hmac.init()
|
||||
c_eng.hmac.update(stream.fromString(message))
|
||||
c_eng.hmac.finish()
|
||||
|
||||
local hash = c_eng.hmac.asHex()
|
||||
|
||||
log.debug("compute_hmac(): hmac-md5 = " .. util.strval(hash) .. " (took " .. (util.time_ms() - start) .. "ms)")
|
||||
|
||||
return hash
|
||||
end
|
||||
|
||||
-- NIC: Network Interface Controller<br>
|
||||
-- utilizes HMAC-MD5 for message authentication, if enabled
|
||||
---@param modem table modem to use
|
||||
function network.nic(modem)
|
||||
local self = {
|
||||
connected = (modem ~= nil),
|
||||
channels = {}
|
||||
}
|
||||
|
||||
---@class nic
|
||||
---@field open function
|
||||
---@field isOpen function
|
||||
---@field close function
|
||||
---@field closeAll function
|
||||
---@field isWireless function
|
||||
---@field getNameLocal function
|
||||
---@field getNamesRemote function
|
||||
---@field isPresentRemote function
|
||||
---@field getTypeRemote function
|
||||
---@field hasTypeRemote function
|
||||
---@field getMethodsRemote function
|
||||
---@field callRemote function
|
||||
local public = {}
|
||||
|
||||
-- connect to a modem peripheral
|
||||
---@param reconnected_modem table
|
||||
function public.connect(reconnected_modem)
|
||||
modem = reconnected_modem
|
||||
self.connected = true
|
||||
|
||||
-- open previously opened channels
|
||||
for _, channel in ipairs(self.channels) do
|
||||
modem.open(channel)
|
||||
end
|
||||
|
||||
-- link all public functions except for transmit
|
||||
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
|
||||
end
|
||||
|
||||
-- flag this NIC as no longer having a connected modem (usually do to peripheral disconnect)
|
||||
function public.disconnect() self.connected = false end
|
||||
|
||||
-- check if this NIC has a connected modem
|
||||
---@nodiscard
|
||||
function public.connected() return self.connected end
|
||||
|
||||
-- check if a peripheral is this modem
|
||||
---@nodiscard
|
||||
---@param device table
|
||||
function public.is_modem(device) return device == modem end
|
||||
|
||||
-- wrap modem functions, then create custom transmit
|
||||
public.connect(modem)
|
||||
|
||||
function public.open(channel)
|
||||
if self.connected then
|
||||
modem.open(channel)
|
||||
|
||||
local already_open = false
|
||||
for i = 1, #self.channels do
|
||||
if self.channels[i] == channel then
|
||||
already_open = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not already_open then
|
||||
table.insert(self.channels, channel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function public.close(channel)
|
||||
if self.connected then
|
||||
modem.close(channel)
|
||||
for i = 1, #self.channels do
|
||||
if self.channels[i] == channel then
|
||||
table.remove(self.channels, i)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function public.closeAll()
|
||||
if self.connected then
|
||||
modem.closeAll()
|
||||
self.channels = {}
|
||||
end
|
||||
end
|
||||
|
||||
-- send a packet, with message authentication if configured
|
||||
---@param dest_channel integer destination channel
|
||||
---@param local_channel integer local channel
|
||||
---@param packet scada_packet packet raw_sendable
|
||||
function public.transmit(dest_channel, local_channel, packet)
|
||||
if self.connected then
|
||||
if c_eng.hmac ~= nil then
|
||||
local start = util.time_ms()
|
||||
local message = textutils.serialize(packet.raw_verifiable(), { allow_repetitions = true, compact = true })
|
||||
local computed_hmac = compute_hmac(message)
|
||||
|
||||
packet.set_mac(computed_hmac)
|
||||
|
||||
log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms")
|
||||
end
|
||||
|
||||
modem.transmit(dest_channel, local_channel, packet.raw_sendable())
|
||||
end
|
||||
end
|
||||
|
||||
-- parse in a modem message as a network packet
|
||||
---@nodiscard
|
||||
---@param side string modem side
|
||||
---@param sender integer sender channel
|
||||
---@param reply_to integer reply channel
|
||||
---@param message any packet sent with or without message authentication
|
||||
---@param distance integer transmission distance
|
||||
---@return scada_packet|nil packet received packet if valid and passed authentication check
|
||||
function public.receive(side, sender, reply_to, message, distance)
|
||||
local packet = nil
|
||||
|
||||
if self.connected then
|
||||
local s_packet = comms.scada_packet()
|
||||
|
||||
-- parse packet as generic SCADA packet
|
||||
s_packet.receive(side, sender, reply_to, message, distance)
|
||||
|
||||
if s_packet.is_valid() then
|
||||
if c_eng.hmac ~= nil then
|
||||
local start = util.time_ms()
|
||||
local packet_hmac = s_packet.mac()
|
||||
local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_verifiable(), { allow_repetitions = true, compact = true }))
|
||||
|
||||
if packet_hmac == computed_hmac then
|
||||
log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms")
|
||||
packet = s_packet
|
||||
else
|
||||
log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms")
|
||||
end
|
||||
else
|
||||
packet = s_packet
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return packet
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
return network
|
Loading…
Reference in New Issue
Block a user