--[[
ģƣSSL SOCKET
ģ鹦ܣSSL SOCKETĴӡշ״̬ά
ģ޸ʱ䣺2017.04.26
]]

--ģ,
local base = _G
local string = require"string"
local table = require"table"
local sys = require"sys"
local ril = require"ril"
local net = require"net"
local rtos = require"rtos"
local sim = require"sim"
local link = require"link"
module("linkssl",package.seeall)

--سõȫֺ
local print = base.print
local pairs = base.pairs
local tonumber = base.tonumber
local tostring = base.tostring
local req = ril.request

local ipstatus,shuting
--socket id0ʼͬʱֵ֧socket8
local MAXLINKS = 7
--socketӱ
local linklist = {}
--Ƿʼ
local inited
local crtinputed,crtpending = "",{}


local function print(...)
	_G.print("linkssl",...)
end

--[[
init
  ʼsslģ
  
ֵ
]]
local function init()
	if not inited then
		inited = true
		req("AT+SSLINIT")		
	end
	local i,item
	for i=1,#crtpending do
		item = table.remove(crtpending,1)
		req(item.cmd,item.arg)
	end
	crtpending = {}
end

--[[
emptylink
  ȡõsocket id
  
ֵõsocket idûпõķnil
]]
local function emptylink()
	for i = 0,MAXLINKS do
		if linklist[i] == nil then
			return i
		end
	end

	return nil
end

--[[
validaction
  ĳsocket idĶǷЧ
  
		idsocket id
		action
ֵtrueЧfalseЧ
]]
local function validaction(id,action)
	--socketЧ
	if linklist[id] == nil then
		print("validaction:id nil",id)
		return false
	end

	--ͬһ״̬ظִ
	if action.."ING" == linklist[id].state then
		print("validaction:",action,linklist[id].state)
		return false
	end

	local ing = string.match(linklist[id].state,"(ING)",-3)

	if ing then
		--ڴʱ,,߹رǿԵ
		if action == "CONNECT" then
			print("validaction: action running",linklist[id].state,action)
			return false
		end
	end

	-- ִ,ִ
	return true
end

--[[
openid
  socketĲϢ
  
		idsocket id
		notifysocket״̬
		recvsocketݽմ
		tagsocket
ֵtrueɹfalseʧ
]]
function openid(id,notify,recv,tag)
	--idԽidsocketѾ
	if id > MAXLINKS or linklist[id] ~= nil then
		print("openid:error",id)
		return false
	end

	local item = {
		notify = notify,
		recv = recv,
		state = "INITIAL",
		tag = tag,
	}

	linklist[id] = item

	--עurc
	ril.regurc("SSL&"..id,urc)
	
	--IP
	if not ipstatus then
		link.setupIP()
	end

	return true
end

--[[
open
  һsocket
  
		notifysocket״̬
		recvsocketݽմ
		tagsocket
ֵnumber͵idʾɹnilʾʧ
]]
function open(notify,recv,tag)
	local id = emptylink()

	if id == nil then
		return nil,"no empty link"
	end

	openid(id,notify,recv,tag)

	return id
end

--[[
close
  رһsocketsocketвϢ
  
		idsocket id
ֵtrueɹfalseʧ
]]
function close(id)
	--Ƿر
	if validaction(id,"CLOSE") == false then
		return false
	end
	--ڹر
	linklist[id].state = "CLOSING"
	--ATر
	req("AT+SSLDESTROY="..id)

	return true
end

--[[
connect
  socketӷ
  
		idsocket id
		protocolЭ飬TCPUDP
		addressַ
		port˿
		chksvrcrtbooleanͣǷ֤
		crtconfigniltableͣ{verifysvrcerts={"filepath1","filepath2",...},clientcert="filepath",clientcertpswd="password",clientkey="filepath"}
ֵɹͬtruefalse
]]
function connect(id,protocol,address,port,chksvrcrt,crtconfig)
	--Ӷ
	if validaction(id,"CONNECT") == false or linklist[id].state == "CONNECTED" then
		return false
	end

	linklist[id].state = "CONNECTING"

	local createstr = string.format("AT+SSLCREATE=%d,\"%s\",%d",id,address..":"..port,chksvrcrt and 0 or 1)
	local configcrtstr,i = {}
	if crtconfig then
		if chksvrcrt and crtconfig.verifysvrcerts then
			for i=1,#crtconfig.verifysvrcerts do
				inputcrt("cacrt",crtconfig.verifysvrcerts[i])
				table.insert(configcrtstr,"AT+SSLCERT=1,"..id..",\"cacrt\",\""..crtconfig.verifysvrcerts[i].."\"")
			end
		end
		if crtconfig.clientcert then
			inputcrt("localcrt",crtconfig.clientcert)
			table.insert(configcrtstr,"AT+SSLCERT=1,"..id..",\"localcrt\",\""..crtconfig.clientcert.."\",\""..(crtconfig.clientcertpswd or "").."\"")
		end
		if crtconfig.clientkey then
			inputcrt("localprivatekey",crtconfig.clientkey)
			table.insert(configcrtstr,"AT+SSLCERT=1,"..id..",\"localprivatekey\",\""..crtconfig.clientkey.."\"")
		end
	end
	local connstr = "AT+SSLCONNECT="..id

	if not ipstatus or shuting then
		--ipδ׼ȼȴ
		linklist[id].pending = createstr.."\r\n"
		for i=1,#configcrtstr do
			linklist[id].pending = linklist[id].pending..configcrtstr[i].."\r\n"
		end
		linklist[id].pending = linklist[id].pending..connstr.."\r\n"
	else
		init()
		--ATӷ
		req(createstr)
		for i=1,#configcrtstr do
			req(configcrtstr[i])
		end
		req(connstr)
	end

	return true
end

--[[
disconnect
  ϿһsocketsocketвϢ
  
		idsocket id
ֵtrueɹfalseʧ
]]
function disconnect(id)
	--Ͽ
	if validaction(id,"DISCONNECT") == false then
		return false
	end
	--socket idӦӻڵȴУû
	if linklist[id].pending then
		linklist[id].pending = nil
		if not ipstatus and linklist[id].state == "CONNECTING" then
			print("disconnect: ip not ready",ipstatus)
			linklist[id].state = "DISCONNECTING"
			return
		end
	end

	linklist[id].state = "DISCONNECTING"
	--ATϿ
	req("AT+SSLDESTROY="..id)

	return true
end

--[[
send
  ݵ
  
		idsocket id
		dataҪ͵
ֵtrueɹfalseʧ
]]
function send(id,data)
	--socketЧsocketδ
	if linklist[id] == nil or linklist[id].state ~= "CONNECTED" then
		print("send:error",id)
		return false
	end

	--ATִݷ
	req(string.format("AT+SSLSEND=%d,%d",id,string.len(data)),data)

	return true
end

--[[
getstate
  ȡһsocket״̬
  
		idsocket id
ֵsocketЧ򷵻״̬򷵻"NIL LINK"
]]
function getstate(id)
	return linklist[id] and linklist[id].state or "NIL LINK"
end

--[[
recv
  ĳsocketݽմ
  
		idsocket id
		lenյݳȣֽΪλ
		dataյ
ֵ
]]
local function recv(id,len,data)
	--socket idЧ
	if linklist[id] == nil then
		print("recv:error",id)
		return
	end
	--socket idӦûעݽմ
	if linklist[id].recv then
		linklist[id].recv(id,data)
	else
		print("recv:nil recv",id)
	end
end

--[[
usersckisactive
  жûsocketǷڼ״̬
  
ֵֻҪκһûsocket״̬ͷtrue򷵻nil
]]
local function usersckisactive()
	for i = 0,MAXLINKS do
		--ûԶsocketûtagֵ
		if linklist[i] and not linklist[i].tag and linklist[i].state=="CONNECTED" then
			return true
		end
	end
end

--[[
usersckntfy
  ûsocket״̬仯֪ͨ
  
		idsocket id
ֵ
]]
local function usersckntfy(id)
	--һڲϢ"USER_SOCKETSSL_CONNECT"֪ͨûsocket״̬仯
	if not linklist[id].tag then sys.dispatch("USER_SOCKETSSL_CONNECT",usersckisactive()) end
end

--[[
sendcnf
  socketݷͽȷ
  
		idsocket id
		resultͽַ
ֵ
]]
local function sendcnf(id,result)
	print("sendcnf",id,result,linklist[id].state)
	--ʧ
	if string.match(result,"ERROR") then
		linklist[id].state = "ERROR"
	end
	--ûע״̬
	linklist[id].notify(id,"SEND",result)
end

--[[
closecnf
  socketرսȷ
  
		idsocket id
		resultرսַ
ֵ
]]
function closecnf(id,result)
	--socket idЧ
	if not id or not linklist[id] then
		print("closecnf:error",id)
		return
	end
	print("closecnf",id,result,linklist[id].state)
	--κεclose,ǳɹϿ,ֱӰӶϿ
	if linklist[id].state == "DISCONNECTING" then
		linklist[id].state = "CLOSED"
		linklist[id].notify(id,"DISCONNECT","OK")
		usersckntfy(id,false)
	--ע,άϢ,urcע
	elseif linklist[id].state == "CLOSING" then		
		local tlink = linklist[id]
		usersckntfy(id,false)
		linklist[id] = nil
		ril.deregurc("SSL&"..id,urc)
		tlink.notify(id,"CLOSE","OK")		
	else
		print("closecnf:error",linklist[id].state)
	end
end

--[[
statusind
  socket״̬ת
  
		idsocket id
		state״ַ̬
ֵ
]]
function statusind(id,state)
	print("statusind",id,state,linklist[id])
	--socketЧ
	if linklist[id] == nil then
		print("statusind:nil id",id)
		return
	end	
	print("statusind1",linklist[id].state)
	if linklist[id].state == "CONNECTING" and string.match(state,"SEND ERROR") then
		return
	end	

	local evt
	--socketӵ״̬߷ӳɹ״̬֪ͨ
	if linklist[id].state == "CONNECTING" or state == "CONNECT OK" then
		--͵¼
		evt = "CONNECT"		
	else
		--״̬͵¼
		evt = "STATE"
	end

	--ӳɹ,Ȼڹر״̬
	if state == "CONNECT OK" then
		linklist[id].state = "CONNECTED"		
	else
		linklist[id].state = "CLOSED"
	end
	--usersckntfyжǷҪ֪ͨûsocket״̬仯
	usersckntfy(id,state == "CONNECT OK")
	--ûע״̬
	linklist[id].notify(id,evt,state)
end

--[[
connpend
  ִIPδ׼ñsocket
  
ֵ
]]
local function connpend()
	for i = 0,MAXLINKS do
		if linklist[i] ~= nil then
			if linklist[i].pending then
				init()
				local item
				for item in string.gmatch(linklist[i].pending,"(.-)\r\n") do
					req(item)
				end
				linklist[i].pending = nil
			end
		end
	end	
end

--[[
ipstatusind
  IP״̬仯
  
		sIP״̬
ֵ
]]
local function ipstatusind(s)
	print("ipstatus:",ipstatus,s)
	if ipstatus ~= s then
		ipstatus = s
		--ִбsocket
		if s then connpend() end
	end
end

--[[
shutcnf
  رIP
  
		resultرսַ
ֵ
]]
local function shutcnf(result)
	shuting = false
	--رճɹ
	if result == "SHUT OK" then
		ipstatusind(false)
		--ϿsocketӣsocketϢ
		for i = 0,MAXLINKS do
			if linklist[i] then
				if linklist[i].state == "CONNECTING" and linklist[i].pending then
					-- δй ʾclose,IPԶ
				elseif linklist[i].state == "INITIAL" then -- δӵҲʾ
				else
					linklist[i].state = "CLOSED"
					linklist[i].notify(i,"STATE","SHUTED")
					usersckntfy(i,false)					
				end
			end
		end
	end
end

--άATͨյһΡĳsocketӷյݡ
--idsocket id
--lenյܳ
--dataѾյ
local rcvd = {id = 0,len = 0,rcvLen = 0,data = {}}

--[[
rcvdfilter
  ATͨȡһ
  
		data
ֵֵһֵʾδݣڶֵʾATͨݹ
]]
local function rcvdfilter(data)
	--ܳΪ0򱾺յݣֱӷ
	if rcvd.len == 0 then
		return data
	end
	--ʣδյݳ
	local restlen = rcvd.len - rcvd.rcvLen
	if  string.len(data) > restlen then -- atͨݱʣδյݶ
		-- ȡ緢
		table.insert(rcvd.data,string.sub(data,1,restlen))
		rcvd.rcvLen = rcvd.rcvLen+restlen
		-- ʣµ԰atк
		data = string.sub(data,restlen+1,-1)
	else
		table.insert(rcvd.data,data)
		rcvd.rcvLen = rcvd.rcvLen+data:len()
		data = ""
	end

	if rcvd.len == rcvd.rcvLen then
		--֪ͨ
		recv(rcvd.id,rcvd.len,table.concat(rcvd.data))
		rcvd.id = 0
		rcvd.len = 0
		rcvd.rcvLen = 0
		rcvd.data = {}
		return data
	else
		return data, rcvdfilter
	end
end

--[[
urc
  ģڡעĵײcoreͨ⴮ϱ֪ͨĴ
  
		dataַ֪ͨϢ
		prefix֪ͨǰ׺
ֵ
]]
function urc(data,prefix)	
	print("urc prefix",prefix)
	--socketյ
	if prefix == "+SSL RECEIVE" then
		local lid,len = string.match(data,",(%d),(%d+)",string.len("+SSL RECEIVE")+1)
		rcvd.id = tonumber(lid)
		rcvd.len = tonumber(len)
		return rcvdfilter,rcvd.len
	--socket״̬֪ͨ
	else
		
		local lid,lstate = string.match(data,"(%d), *([%u :%d]+)")
		print("urc data",data,lid,lstate)
		
		if string.find(lstate,"ERROR:")==1 then return end

		if lid then
			lid = tonumber(lid)
			statusind(lid,lstate)
		end
	end
end

--[[
getresult
  socket״ַ̬
  
		strsocket״ַ̬SSL&1,SEND OK
ֵsocket״̬socket id,SEND OK
]]
local function getresult(str)
	return str == "ERROR" and str or string.match(str,"%d, *([%u :%d]+)")
end

local function emptylink()
	for i = 0,MAXLINKS do
		if linklist[i] == nil then
			return i
		end
	end

	return nil
end

--[[
term
  رsslģ
  
ֵ
]]
local function term()
	if inited then
		local valid,i
		for i = 0,MAXLINKS do
			if linklist[i] and linklist[i].state~="CLOSED" and linklist[i].state~="INITIAL" then
				valid = true
				break
			end
		end
		if not valid then
			inited = false
			req("AT+SSLTERM")
			crtinputed = ""
		end
	end
end

--[[
rsp
  ģڡͨ⴮ڷ͵ײcoreATӦ
  
		cmdӦӦAT
		successATִнtruefalse
		responseATӦеִнַ
		intermediateATӦемϢ
ֵ
]]
local function rsp(cmd,success,response,intermediate)
	local prefix = string.match(cmd,"AT(%+%u+)")
	local id = tonumber(string.match(cmd,"AT%+%u+=(%d)"))
	
	print("rsp",id,prefix,response)
	
	if prefix == "+SSLCONNECT" then
		--statusind(id,getresult(response))
		if response == "ERROR" then
			statusind(id,"ERROR")
		end
	--ݵӦ
	elseif prefix == "+SSLSEND" then
		sendcnf(id,getresult(response))
	--رsocketӦ
	elseif prefix == "+SSLDESTROY" then
		closecnf(id,getresult(response))	
		term()
	end
end

local function ipshutingind(s)
	if s then
		shuting = true
	else
		shutcnf("SHUT OK")
	end
end

local function gprsind(s)
	if s and base.next(linklist) and not ipstatus then
		link.setupIP()
	end
	return true
end

function inputcrt(t,f,d)
	if string.match(crtinputed,t..f.."&") then return end
	if not crtpending then crtpending={} end
	if d then
		table.insert(crtpending,{cmd="AT+SSLCERT=0,\""..t.."\",\""..f.."\",1,"..string.len(d),arg=d})
	else
		local path = (string.sub(f,1,1)=="/") and f or ("/ldata/"..f)
		local fconfig = io.open(path,"rb")
		if not fconfig then print("inputcrt err open",path) return end
		local s = fconfig:read("*a")
		fconfig:close()
		table.insert(crtpending,{cmd="AT+SSLCERT=0,\""..t.."\",\""..f.."\",1,"..string.len(s),arg=s})
	end
	crtinputed = crtinputed..t..f.."&"
end

local procer =
{
	IP_STATUS_IND = ipstatusind,
	IP_SHUTING_IND = ipshutingind,
	NET_GPRS_READY = gprsind,
}

sys.regapp(procer)
--עurc֪ͨĴ
ril.regurc("+SSL RECEIVE",urc)
--עATӦ
ril.regrsp("+SSLCONNECT",rsp)
ril.regrsp("+SSLSEND",rsp)
ril.regrsp("+SSLDESTROY",rsp)

link.regipstatusind()
