2023-04-18 17:55:18 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
local log = require("scada-common.log")
|
|
|
|
local mqueue = require("scada-common.mqueue")
|
|
|
|
local util = require("scada-common.util")
|
2023-04-18 17:55:18 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
local iocontrol = require("coordinator.iocontrol")
|
2023-04-18 17:55:18 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
local pocket = require("coordinator.session.pocket")
|
2023-04-18 17:55:18 +00:00
|
|
|
|
|
|
|
local apisessions = {}
|
|
|
|
|
|
|
|
local self = {
|
2024-02-19 02:34:25 +00:00
|
|
|
nic = nil, ---@type nic
|
|
|
|
config = nil, ---@type crd_config
|
2023-04-18 17:55:18 +00:00
|
|
|
next_id = 0,
|
|
|
|
sessions = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
-- PRIVATE FUNCTIONS --
|
|
|
|
|
|
|
|
-- handle a session output queue
|
2023-06-06 23:41:09 +00:00
|
|
|
---@param session pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
local function _api_handle_outq(session)
|
|
|
|
-- record handler start time
|
|
|
|
local handle_start = util.time()
|
|
|
|
|
|
|
|
-- process output queue
|
|
|
|
while session.out_queue.ready() do
|
|
|
|
-- get a new message to process
|
|
|
|
local msg = session.out_queue.pop()
|
|
|
|
|
|
|
|
if msg ~= nil then
|
|
|
|
if msg.qtype == mqueue.TYPE.PACKET then
|
|
|
|
-- handle a packet to be sent
|
2024-02-19 02:34:25 +00:00
|
|
|
self.nic.transmit(self.config.PKT_Channel, self.config.CRD_Channel, msg.message)
|
2023-04-18 17:55:18 +00:00
|
|
|
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
|
|
|
-- handle instruction/notification
|
|
|
|
elseif msg.qtype == mqueue.TYPE.DATA then
|
|
|
|
-- instruction/notification with body
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- max 100ms spent processing queue
|
|
|
|
if util.time() - handle_start > 100 then
|
2023-08-26 15:54:58 +00:00
|
|
|
log.warning("API: out queue handler exceeded 100ms queue process limit")
|
|
|
|
log.warning(util.c("API: offending session: ", session))
|
2023-04-18 17:55:18 +00:00
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- cleanly close a session
|
2023-06-06 23:41:09 +00:00
|
|
|
---@param session pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
local function _shutdown(session)
|
|
|
|
session.open = false
|
|
|
|
session.instance.close()
|
|
|
|
|
|
|
|
-- send packets in out queue (namely the close packet)
|
|
|
|
while session.out_queue.ready() do
|
|
|
|
local msg = session.out_queue.pop()
|
|
|
|
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
2024-02-19 02:34:25 +00:00
|
|
|
self.nic.transmit(self.config.PKT_Channel, self.config.CRD_Channel, msg.message)
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-08-26 15:54:58 +00:00
|
|
|
log.debug(util.c("API: closed session ", session))
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- PUBLIC FUNCTIONS --
|
|
|
|
|
|
|
|
-- initialize apisessions
|
2024-02-19 02:24:30 +00:00
|
|
|
---@param nic nic network interface
|
2024-02-19 02:34:25 +00:00
|
|
|
---@param config crd_config coordinator config
|
|
|
|
function apisessions.init(nic, config)
|
2023-06-21 23:04:39 +00:00
|
|
|
self.nic = nic
|
2024-02-19 02:34:25 +00:00
|
|
|
self.config = config
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- find a session by remote port
|
|
|
|
---@nodiscard
|
2023-06-06 23:41:09 +00:00
|
|
|
---@param source_addr integer
|
|
|
|
---@return pkt_session_struct|nil
|
|
|
|
function apisessions.find_session(source_addr)
|
2023-04-18 17:55:18 +00:00
|
|
|
for i = 1, #self.sessions do
|
2023-06-06 23:41:09 +00:00
|
|
|
if self.sessions[i].s_addr == source_addr then return self.sessions[i] end
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
-- establish a new API session
|
|
|
|
---@nodiscard
|
2024-06-29 02:27:55 +00:00
|
|
|
---@param source_addr integer pocket computer ID
|
|
|
|
---@param i_seq_num integer initial sequence number to use next
|
|
|
|
---@param version string pocket version
|
2023-04-18 17:55:18 +00:00
|
|
|
---@return integer session_id
|
2024-06-29 02:27:55 +00:00
|
|
|
function apisessions.establish_session(source_addr, i_seq_num, version)
|
2023-06-06 23:41:09 +00:00
|
|
|
---@class pkt_session_struct
|
|
|
|
local pkt_s = {
|
2023-04-18 17:55:18 +00:00
|
|
|
open = true,
|
|
|
|
version = version,
|
2023-06-06 23:41:09 +00:00
|
|
|
s_addr = source_addr,
|
2023-04-18 17:55:18 +00:00
|
|
|
in_queue = mqueue.new(),
|
|
|
|
out_queue = mqueue.new(),
|
2023-06-06 23:41:09 +00:00
|
|
|
instance = nil ---@type pkt_session
|
2023-04-18 17:55:18 +00:00
|
|
|
}
|
|
|
|
|
2023-06-06 23:41:09 +00:00
|
|
|
local id = self.next_id
|
2023-04-18 17:55:18 +00:00
|
|
|
|
2024-06-29 02:27:55 +00:00
|
|
|
pkt_s.instance = pocket.new_session(id, source_addr, i_seq_num, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout)
|
2023-06-06 23:41:09 +00:00
|
|
|
table.insert(self.sessions, pkt_s)
|
2023-04-18 17:55:18 +00:00
|
|
|
|
2023-06-06 23:41:09 +00:00
|
|
|
local mt = {
|
|
|
|
---@param s pkt_session_struct
|
|
|
|
__tostring = function (s) return util.c("PKT [", id, "] (@", s.s_addr, ")") end
|
|
|
|
}
|
|
|
|
|
|
|
|
setmetatable(pkt_s, mt)
|
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
iocontrol.fp_pkt_connected(id, version, source_addr)
|
2023-08-26 15:54:58 +00:00
|
|
|
log.debug(util.c("API: established new session: ", pkt_s))
|
2023-06-06 23:41:09 +00:00
|
|
|
|
|
|
|
self.next_id = id + 1
|
2023-04-18 17:55:18 +00:00
|
|
|
|
|
|
|
-- success
|
2023-06-06 23:41:09 +00:00
|
|
|
return pkt_s.instance.get_id()
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- attempt to identify which session's watchdog timer fired
|
|
|
|
---@param timer_event number
|
|
|
|
function apisessions.check_all_watchdogs(timer_event)
|
|
|
|
for i = 1, #self.sessions do
|
2023-06-06 23:41:09 +00:00
|
|
|
local session = self.sessions[i] ---@type pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
if session.open then
|
|
|
|
local triggered = session.instance.check_wd(timer_event)
|
|
|
|
if triggered then
|
2023-08-26 15:54:58 +00:00
|
|
|
log.debug(util.c("API: watchdog closing session ", session, "..."))
|
2023-04-18 17:55:18 +00:00
|
|
|
_shutdown(session)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- iterate all the API sessions
|
|
|
|
function apisessions.iterate_all()
|
|
|
|
for i = 1, #self.sessions do
|
2023-06-06 23:41:09 +00:00
|
|
|
local session = self.sessions[i] ---@type pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
|
|
|
|
if session.open and session.instance.iterate() then
|
|
|
|
_api_handle_outq(session)
|
|
|
|
else
|
|
|
|
session.open = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- delete all closed sessions
|
|
|
|
function apisessions.free_all_closed()
|
|
|
|
local f = function (session) return session.open end
|
|
|
|
|
2023-06-06 23:41:09 +00:00
|
|
|
---@param session pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
local on_delete = function (session)
|
2023-08-26 15:54:58 +00:00
|
|
|
log.debug(util.c("API: free'ing closed session ", session))
|
2023-04-18 17:55:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
util.filter_table(self.sessions, f, on_delete)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- close all open connections
|
|
|
|
function apisessions.close_all()
|
|
|
|
for i = 1, #self.sessions do
|
2023-06-06 23:41:09 +00:00
|
|
|
local session = self.sessions[i] ---@type pkt_session_struct
|
2023-04-18 17:55:18 +00:00
|
|
|
if session.open then _shutdown(session) end
|
|
|
|
end
|
|
|
|
|
|
|
|
apisessions.free_all_closed()
|
|
|
|
end
|
|
|
|
|
|
|
|
return apisessions
|