#!/usr/bin/lua
--[[

    Copyright 2010 MINA Group, Facultad de Ingenieria, Universidad de la
    Republica, Uruguay.

    LUPA is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    LUPA is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with the RAN System.  If not, see <http://www.gnu.org/licenses/>.

--]]

--[[
    This is the main script for the LUPA package.
    It centralizes the communication with the chosen bus as well as the
    execution of the policies.
    Subscritpions will be issued for the three LUPA modules:
        - PDP: The policy decision point.
        - PEP: The policy enforcement point.
        - RMOON: The monitoring service.
    This implies that messages issued for PDP/PEP/RMOON will be received by
    this LUPA script.
    
    As the bus messages arrives, LUPA will appropiately distribute it to the
    right module and call the different functions aimed at proccessing the bus
    messages.
    
--]]

package.path= "lupa/?.lua;" .. package.path .. ";lupa/lib/?.lua;lupa/rmoon/?.lua;lupa/pep/?.lua;lupa/pdp/?.lua"

io.stdout:setvbuf("line")

require("socket")

-- The three sockets necessary for communication with the RNR/RON bus.
local skt_pep, skt_pdp, skt_rmoon

local configuration=require("configuration")
if arg[1] then configuration.load(arg[1]) end

local to_host=configuration.upstream
local to_port=configuration.rnr_port
local my_host=configuration.my_host

local trap_generation_interval=configuration.trap_generation_interval
local pdp_evaluation_interval=configuration.pdp_evaluation_interval
print("CONF my_host", my_host)
print("CONF to_port", to_port)
print("CONF upstream", to_host)

local messages=require("messages")

print ("CONF enable_pep", configuration.enable_pep)
print ("CONF enable_pdp", configuration.enable_pdp)
print ("CONF enable_rmoon", configuration.enable_rmoon)
local pep, pdp, rmoon
if configuration.enable_pep then pep=require("pep") end
if configuration.enable_pdp then pdp=require("pdp") end
if configuration.enable_rmoon then rmoon=require("rmoon") end

local util=require("util")
local unescape=util.unescape

local sha1, sign_message
if configuration.use_sha1 then
	sha1=require('sha1')
	sign_message=sha1.hmac_sha1_message
else
	print("SHA1 signing disabled")
	sign_message=function(...) return "n/a" end
end


--local hello = "HELLO\nsubscriptor_id=" ..name .."\nEND\n"

--builds a string to be sent to the router, encoding a output of the fsm
local function generate_pdp_output(out)
    --print("Generating PDP output")
	if out._type=="subscription" then
		out._type=nil
		return (messages.generate_subscription(configuration.my_name_pdp,out))
	end
	if out._type=="notification_raw" then
		out._type=nil
		return (messages.generate_notification_raw(configuration.my_name_pdp,out))
	end
	if out._type=="action" or not out._type then --default type
		out._type=nil
		--print("An action is generated")
		local pepino = messages.generate_action(configuration.my_name_pdp,out)
		--print(pepino)
		return (messages.generate_action(configuration.my_name_pdp,out))
	end

	--fallback
	return ""
end

local function parse_params(data)
	local params={}
	local k, v
	for _, linea in ipairs(data) do
		k, v=  string.match(linea, "^%s*(.-)%s*=%s*(.-)%s*$")
		if k and v then
			--print ("+",k,v)
			params[k]=v
		else
			print ("unparseable line", linea)
		end
	end

	if configuration.use_sha1 then
		local signstatus=sign_message(params)
		print("SHA1 signature: ", signstatus)
		if signstatus~='ok' then 
			print("WARN: Purging message (signature check failure)")
			params={} 
		end
	end

	for k, v in pairs(params) do
		params[k]=unescape(v)
	end

	return params
end

local function process_pep(params)
	local message_type, command, target = params.message_type, params.command, params.target
		
	--procesamos segun accion
	if message_type == "action" and command then
		local response
		if pep.commands[command] then
			print ("IN pep action",command)
			local response = pep.commands[command](params)
			if response then
				local msg=messages.generate_response(configuration.my_name_pep,params, response)
				_,err_pep = skt_pep:send(msg)
   			--if err_pep then print ("Error sending response", err_pep) end
			end
		else
			print ("ERR: unknown command",command)
		end	
	end
end

local function process_pdp(params)
	local message_type, command, target = params.message_type, params.command, params.target
		
	--procesamos segun accion
	if message_type == "action" and command then
		local response, outgoing_s, outgoing_n
		if pdp.commands[command] then
			print ("IN pdp action",command)
			--local messages
			response, outgoing_s, outgoing_n = pdp.commands[command](params)
			--ademas, devolvemos la salida de inicializar...
			if response then
				local msg=messages.generate_response(configuration.my_name_pdp,params, response)
				local _,err_pdp = skt_pdp:send(msg)
 				--if err_pdp then print ("Error sending response", err_pdp) end
			end
			if outgoing_s then
				for _, out in ipairs(outgoing_s) do
          local m = messages.generate_subscription(configuration.my_name_pdp,out)
					local _,err_pdp = skt_pdp:send( m )
 					--if err_pdp then print ("Error sending outgoing", err_pdp) end
          --print("An action was pumped in the socket")
				end
			end						
			if outgoing_n then
				for _, out in ipairs(outgoing_n) do
          local m = messages.generate_action(configuration.my_name_pdp,out)
					local _,err_pdp = skt_pdp:send( m )
 					--if err_pdp then print ("Error sending outgoing", err_pdp) end
          --print("An action was pumped in the socket")
				end
			end					
    else
			print ("ERR: unknown command",command)
		end
	
	end

	--TODO decidir message_type que evaluar en la fsm: trap y/o action y/o response
	--registramos el evento para la maquina de estados
	--if message_type == "trap" then
	--if message_type ~= "action" then
  local outgoing=pdp.incomming_event(params)
    --print("Elementos outgoing : ", table.maxn(outgoing))
    for _, out in ipairs(outgoing) do
        local _,err_pdp = skt_pdp:send( generate_pdp_output(out) )
        --if err_pdp then print ("Error sending outgoing", err_pdp)end
        --print("An action was pumped in the socket")
    end		
	--end
end

local function process_rmoon(params)
	local message_type, command, target = params.message_type, params.command, params.target
		
	--procesamos segun accion
	if message_type == "action" and command then
		local response
		if rmoon.commands[command] then
			print ("IN rmoon action",command)
			response = rmoon.commands[command](params)
			if response then
				local msg=messages.generate_response(configuration.my_name_rmoon,params, response)
				_,err_rmoon = skt_rmoon:send(msg)
 				--if err_rmoon then print ("Error sending response", err_rmoon)end
			end
		else
			print ("ERR: unknown command",command)
		end
	end
end

while true do
	local err,err_pep,err_pdp,err_rmoon
	print("Connecting...")
	if pep then skt_pep = socket.connect(to_host, to_port) end
	if pdp then skt_pdp = socket.connect(to_host, to_port) end
	if rmoon then skt_rmoon = socket.connect(to_host, to_port) end

	local last_rmoon_timestamp = 0
	local last_pdp_timestamp = 0
	if skt_pep or skt_pdp or skt_rmoon then	
		print("Connected.")
		local skts={}
    skts[#skts+1] = skt_pep
    skts[#skts+1] = skt_pdp
    skts[#skts+1] = skt_rmoon
		for _, skt in ipairs(skts) do skt:settimeout(nil) end

		local subsnid = "_sub_"..tostring(math.random(2^30)) 
		local subsn_pep = "SUBSCRIBE\nhost=".. configuration.my_host 
				.."\nservice=".. configuration.my_name_pep .."\nsubscription_id=pep"..subsnid
				.. "\nFILTER\ntarget_host=".. configuration.my_host .."\ntarget_service=".. configuration.my_name_pep .."\nEND\n"
		local subsn_pdp = "SUBSCRIBE\nhost=".. configuration.my_host 
				.."\nservice=".. configuration.my_name_pdp .."\nsubscription_id=pdp"..subsnid
				.. "\nFILTER\ntarget_host=".. configuration.my_host .."\ntarget_service=".. configuration.my_name_pdp .."\nEND\n"
		local subsn_rmoon = "SUBSCRIBE\nhost=".. configuration.my_host 
				.."\nservice=".. configuration.my_name_rmoon .."\nsubscription_id=rmoon"..subsnid
				.. "\nFILTER\ntarget_host=".. configuration.my_host .."\ntarget_service=".. configuration.my_name_rmoon .."\nEND\n"
		
    if skt_pep then 
      _,err_pep = skt_pep:send(subsn_pep or "") 
      if err_pep then print ("Error sending subsn_pep", err_pep)end
    end
    if skt_pdp then 
      _,err_pdp = skt_pdp:send(subsn_pdp or "")
      if err_pdp then print ("Error sending subsn_pdp", err_pdp)end
    end
    if skt_rmoon then 
      _,err_rmoon = skt_rmoon:send(subsn_rmoon or "")
      if err_rmoon then print ("Error sending subsn_rmoon", err_rmoon)end
    end
		print("Subscribed.")
		
		--client:settimeout(configuration.time_step)
		print("Tick", configuration.time_step)
		
		print("===Listening===")
	
		local line, data_pep, data_pdp, data_rmoon, ts
		while err_pep ~= "closed" or err_pdp ~= "closed" or err_rmoon ~= "closed" do
			local data_skts, _, err = socket.select(skts, nil, configuration.time_step)
			io.flush()
			ts = socket.gettime()
			--verify pending jobs
			--process watchers
			if rmoon and ts - last_rmoon_timestamp >= trap_generation_interval then
--print('--', ts)
				local traps=rmoon.generate_traps()
				for _, trap in ipairs(traps) do
					_,err_rmoon = skt_rmoon:send( messages.generate_trap(configuration.my_name_rmoon,trap) )
					--if err_rmoon then print ("Error sending trap", err_rmoon)end
				end
				last_rmoon_timestamp=ts
			end
			--process policy in pdp
			if pdp and ts - last_pdp_timestamp >= pdp_evaluation_interval then
				local outgoing=pdp.tick()
				for _, notif in ipairs(outgoing) do
					_,err_pdp = skt_pdp:send( messages.generate_action(configuration.my_name_pdp,notif) )
 					--if err_pdp then print ("Error sending notif", err_pdp)end
				end
				last_pdp_timestamp=ts
			end
			if err~="timeout" then
				if skt_pep and data_skts[skt_pep] then
					line, err_pep = skt_pep:receive()
					--accumulate message lines
					if data_pep then
						if line=="END" then
							local params=parse_params(data_pep)
							process_pep(params)
							data_pep = nil
						else
							table.insert(data_pep, line)
						end
					else
						if line=="NOTIFICATION" then
							data_pep = {}
						end
					end
				end
				if skt_pdp and data_skts[skt_pdp] then
					line, err_pdp = skt_pdp:receive()
					--accumulate message lines
					if data_pdp then
						if line=="END" then
							local params=parse_params(data_pdp)
							process_pdp(params)
							data_pdp = nil
						else
							table.insert(data_pdp, line)
						end
					else
						if line=="NOTIFICATION" then
							data_pdp = {}
						end
					end
				end
				if skt_rmoon and data_skts[skt_rmoon] then
					line, err_rmoon = skt_rmoon:receive()
					--accumulate message lines
					if data_rmoon then
						if line=="END" then
							local params=parse_params(data_rmoon)
							process_rmoon(params)
							data_rmoon = nil
						else
							table.insert(data_rmoon, line)
						end
					else
						if line=="NOTIFICATION" then
							data_rmoon = {}
						end
					end
				end
			end
		end
	end
		
	--wait to reconnect
	print ("Connection closed, waiting")
	if skt_pep then skt_pep:close() end
	if skt_pdp then skt_pdp:close() end
	if skt_rmoon then skt_rmoon:close() end
	socket.sleep(configuration.sleep_on_reconnect or 1)
end

if skt_pep then skt_pep:close() end
if skt_pdp then skt_pdp:close() end
if skt_rmoon then skt_rmoon:close() end
print("Closed")

