--ģ,
require"sys"
require"misc"
require"link"
require"socketssl"
require"crypto"
module(...,package.seeall)


local ssub,schar,smatch,sbyte,slen,sfind = string.sub,string.char,string.match,string.byte,string.len,string.find

--ƼȨ
local SCK_IDX,PROT,ADDR,PORT = socketssl.SCK_MAX_CNT+1,"TCP","iot-auth.cn-shanghai.aliyuncs.com",443
--밢ƼȨsocket״̬
local linksta
--һڵĶӺ̨ʧܣ᳢ΪRECONN_PERIOD룬RECONN_MAX_CNT
--һڶûӳɹȴRECONN_CYCLE_PERIOD·һ
--RECONN_CYCLE_MAX_CNTεڶûӳɹ
local RECONN_MAX_CNT,RECONN_PERIOD,RECONN_CYCLE_MAX_CNT,RECONN_CYCLE_PERIOD = 3,10,3,20
--reconncnt:ǰڣѾĴ
--reconncyclecnt:ٸڣûӳɹ
--һӳɹḴλ
--conning:Ƿڳ
local reconncnt,reconncyclecnt,conning = 0,0
--ƷʶƷԿ豸豸Կ
local productkey,productsecret,devicename,devicesecret
--ӼȨյģеЧ
local rcvbuf,rcvalidbody = "",""

--[[
print
  ӡӿڣļедӡaliyuniotauthǰ׺
  
ֵ
]]
local function print(...)
	_G.print("aliyuniotauthssl",...)
end

local function getdevice(s)
	if s=="name" then
		return type(devicename)=="function" and devicename() or (devicename or misc.getimei())
	elseif s=="secret" then
		return type(devicesecret)=="function" and devicesecret() or (devicesecret or misc.getsn())
	end
end

--[[
snd
  ÷ͽӿڷ
  
        data͵ݣڷͽ¼ntfyУḳֵitem.data
		para͵Ĳڷͽ¼ntfyУḳֵitem.para 
ֵ÷ͽӿڵĽݷǷɹĽݷǷɹĽntfyеSEND¼֪ͨtrueΪɹΪʧ
]]
function snd(data,para)
	return socketssl.send(SCK_IDX,data,para)
end

--[[
postsnd
  POSTĵȨ
  
		typ
ֵ
]]
local function postsnd(typ)	
	local data = "clientId"..getdevice("name").."deviceName"..getdevice("name").."productKey"..productkey
	local signkey = getdevice("secret")
	local sign = crypto.hmac_md5(data,slen(data),signkey,slen(signkey))
	local body = "productKey="..productkey.."&sign="..sign.."&clientId="..getdevice("name").."&deviceName="..getdevice("name")
	local head = "POST /auth/devicename HTTP/1.1\r\n" .. "Host: "..ADDR.."\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: "..slen(body).."\r\n\r\n"
	snd(head..body,"POSTSND")
end

--[[
preproc
  ȨԤ
  
ֵ
]]
function preproc()
	print("preproc",linksta)
	if linksta then
		postsnd()
	end
end

--[[
sndcb
  ݷͽ
            
		itemtableͣ{data=,para=}ϢشĲݣsocketssl.sendʱĵ2͵3ֱΪdatparitem={data=dat,para=par}
		result boolͣͽtrueΪɹΪʧ
ֵ
]]
local function sndcb(item,result)
	print("sndcb",item.para,result)
	if not item.para then return end
	if item.para=="POSTSND" then
		sys.timer_start(reconn,RECONN_PERIOD*1000)
	end
end


--[[
reconn
  ̨
        һڵĶӺ̨ʧܣ᳢ΪRECONN_PERIOD룬RECONN_MAX_CNT
        һڶûӳɹȴRECONN_CYCLE_PERIOD·һ
        RECONN_CYCLE_MAX_CNTεڶûӳɹ
  
ֵ
]]
function reconn()
	print("reconn",reconncnt,conning,reconncyclecnt)
	--conningʾڳӺ̨һҪжϴ˱пܷ𲻱Ҫreconncntӣʵʵ
	if conning then return end
	--һڵ
	if reconncnt < RECONN_MAX_CNT then		
		reconncnt = reconncnt+1
		socketssl.disconnect(SCK_IDX)
	--һڵʧ
	else
		reconncnt,reconncyclecnt = 0,reconncyclecnt+1
		if reconncyclecnt >= RECONN_CYCLE_MAX_CNT then
			sys.restart("connect fail")
		end
		link.shut()
	end
end

--[[
ntfy
  socket״̬Ĵ
  
        idxnumberͣsocket.luaάsocket idxsocketssl.connectʱĵһͬԺԲ
        evtstringͣϢ¼
		result boolͣϢ¼trueΪɹΪʧ
		itemtableͣ{data=,para=}ϢشĲݣĿǰֻSEND͵¼õ˴˲socketssl.sendʱĵ2͵3ֱΪdatparitem={data=dat,para=par}
ֵ
]]
function ntfy(idx,evt,result,item)
	print("ntfy",evt,result,item)
	--ӽsocketssl.connect첽¼
	if evt == "CONNECT" then
		conning = false
		--ӳɹ
		if result then
			reconncnt,reconncyclecnt,linksta,rcvbuf,rcvbody = 0,0,true,"",""
			--ֹͣʱ
			sys.timer_stop(reconn)
			preproc()
		--ʧ
		else
			--RECONN_PERIOD
			sys.timer_start(reconn,RECONN_PERIOD*1000)
		end	
	--ݷͽsocketssl.send첽¼
	elseif evt == "SEND" then
		if item then
			sndcb(item,result)
		end
		--ʧܣRECONN_PERIOD̨Ҫreconnʱsocket״̬ȻCONNECTEDᵼһֱϷ
		--if not result then sys.timer_start(reconn,RECONN_PERIOD*1000) end
		if not result then socketssl.disconnect(SCK_IDX) end
	--ӱϿ
	elseif evt == "STATE" and result == "CLOSED" then
		linksta = false
		socketssl.close(SCK_IDX)
	--Ͽlink.shut첽¼
	elseif evt == "STATE" and result == "SHUTED" then
		linksta = false
		connect()
	--Ͽsocketssl.disconnect첽¼
	elseif evt == "DISCONNECT" then
		linksta = false
		connect()
	end
	--Ͽ·
	if smatch((type(result)=="string") and result or "","ERROR") then
		socketssl.disconnect(SCK_IDX)
	end
end

--[[
parsevalidbody
  ȨصЧ
  
ֵ
]]
local function parsevalidbody()
	print("parsevalidbody")
	local tjsondata = json.decode(rcvalidbody)
	
	print("message",tjsondata["message"])
	if tjsondata["message"]~="success" then print("parsevalidbody message err") return end
	
	local iotId = tjsondata["data"]["iotId"]
	print("iotId",iotId)
	if not iotId or iotId=="" then print("parsevalidbody iotId err") return end
	
	local iotToken = tjsondata["data"]["iotToken"]
	print("iotToken",iotToken)
	if not iotToken or iotToken=="" then print("parsevalidbody iotToken err") return end
	
	local ports,host,rmqtt = {}
	if tjsondata["data"]["resources"] then
		if tjsondata["data"]["resources"]["mqtt"] then
			rmqtt,host = true,tjsondata["data"]["resources"]["mqtt"]["host"]
			table.insert(ports,tjsondata["data"]["resources"]["mqtt"]["port"])
			print("host",host)
			print("port",tjsondata["data"]["resources"]["mqtt"]["port"])
		end
	end
	
	sys.dispatch("ALIYUN_DATA_BGN",rmqtt and host or productkey..".iot-as-mqtt.cn-shanghai.aliyuncs.com",#ports~=0 and ports or {1883},getdevice("name"),iotId,iotToken)	
	sys.timer_stop(reconn)
	return true
end

--[[
parse
  Ȩص
  
ֵ
]]
local function parse()
	local headend = sfind(rcvbuf,"\r\n\r\n")
	if not headend then print("parse wait head end") return end
	
	local headstr = ssub(rcvbuf,1,headend+3)
	--if not smatch(headstr,"HTTP/%d*%.*%d* 200") then print("parse no 200 OK") return end
	
	local contentflg
	if smatch(headstr,"Transfer%-Encoding: chunked") or smatch(headstr,"Transfer%-Encoding: Chunked") then
		contentflg = "chunk"
	elseif smatch(headstr,"Content%-Length: %d+") then
		contentflg = tonumber(smatch(headstr,"Content%-Length: (%d+)"))
	end
	if not contentflg then print("parse contentflg error") return end
	
	local rcvbody = ssub(rcvbuf,headend+4,-1)
	if contentflg=="chunk" then	
		rcvalidbody = ""
		if not smatch(rcvbody,"0\r\n\r\n") then print("parse wait chunk end") return end
		local h,t,len
		while true do
			h,t,len = sfind(rcvbody,"(%w+)\r\n")
			if len then
				len = tonumber(len,16)
				if len==0 then break end
				rcvalidbody = rcvalidbody..ssub(rcvbody,t+1,t+len)
				rcvbody = ssub(rcvbody,t+len+1,-1)
			else
				print("parse chunk len err ")
				return
			end
		end
	else
		if slen(rcvbody)~=contentflg then print("parse wait content len end") return end
		rcvalidbody = rcvbody
	end
	
	rcvbuf = ""
	if not parsevalidbody() then sys.dispatch("ALIYUN_AUTH_ERROR") end
	socketssl.close(SCK_IDX)
end

--[[
rcv
  socketݵĴ
  
        idx socketssl.luaάsocket idxsocketssl.connectʱĵһͬԺԲ
        dataյ
ֵ
]]
function rcv(idx,data)
	print("rcv",data)
	rcvbuf = rcvbuf..data
	parse()
end

--[[
connect
  ƼȨӣ
        Ѿ׼ãӺ̨ᱻ𣬵׼ԶȥӺ̨
		ntfysocket״̬Ĵ
		rcvsocketݵĴ
  
ֵ
]]
function connect()
	socketssl.connect(SCK_IDX,PROT,ADDR,PORT,ntfy,rcv)
	conning = true
end

--[[
authbgn
  Ȩ
  
ֵ
]]
local function authbgn(pkey,psecret,dname,dsecret)
	productkey,productsecret,devicename,devicesecret = pkey,psecret,dname,dsecret
	connect()
end

local procer =
{
	ALIYUN_AUTH_BGN = authbgn,
}

sys.regapp(procer)

