Logic Machine Forum
what about Obix - Printable Version

+- Logic Machine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Gateway (https://forum.logicmachine.net/forumdisplay.php?fid=10)
+--- Thread: what about Obix (/showthread.php?tid=237)



what about Obix - domotiqa - 02.03.2016

IS it planned to develop Obix gateway like you did with Bacnet ?

What do you think about it.


RE: what about Obix - edgars - 03.03.2016

by our information people are not using Obix too widely. You can realize same tasks on web services.
Maybe you can provide your arguments why it is worth to add support of Obix?
Thanks!


RE: what about Obix - domotiqa - 03.03.2016

no it was just a question, because I readed this:
http://www.knx.org/fileadmin/downloads/05%20-%20KNX%20Partners/03%20-%20Becoming%20a%20KNX%20Scientific%20Partner/2006-11%20Scientific%20Conference%20Papers%20Vienna/09_neugschwandtner-web_services-knxsci06-website.pdf

And in this doc, Obix looks more open... It was just in order to have your though


RE: what about Obix - Erwin van der Zwart - 05.03.2016

(03.03.2016, 10:00)domotiqa Wrote: no it was just a question, because I readed this:
http://www.knx.org/fileadmin/downloads/05%20-%20KNX%20Partners/03%20-%20Becoming%20a%20KNX%20Scientific%20Partner/2006-11%20Scientific%20Conference%20Papers%20Vienna/09_neugschwandtner-web_services-knxsci06-website.pdf

And in this doc, Obix looks more open... It was just in order to have your though

Hi Domotiqa,

Here are 2 functions i use to communicate with our SmartStruxure Lite controller with oBIX. Maybe you can use it.

-- function to get data from mpm
function get_data_from_mpm(mpm_settings,object,item)
  local url = "http://" .. mpm_settings.username .. ":" .. mpm_settings.password .. "@" .. mpm_settings.ip .. "/obix/network/" .. mpm_settings.node .. "/DEV" .. mpm_settings.instance .. "/" .. object .. "/" .. item
  local b, c, h = mpm.request(url)
  local value = b:match([[val="(.-)"]])
  local value = tonumber(value)
  return value
end

-- function to post data to mpm
function post_data_to_mpm(mpm_settings,object,item,value)
  local url = "http://" .. mpm_settings.username .. ":" .. mpm_settings.password .. "@" .. mpm_settings.ip .. "/obix/network/" .. mpm_settings.node .. "/DEV" .. mpm_settings.instance .. "/" .. object .. "/" .. item .. "/"
  local reqBody = [[<intl val="]] .. value .. [["/>]]
  local headers = {["Content-Type"] = "text/xml", ["Content-Length"] = #reqBody}
  local respTable = {}
  local returnList = {client = {}, code = {}, headers = {}, status = {}}
  local source=ltn12.source.string(reqBody)
  local sink=ltn12.sink.table(respTable)
  local result = mpm.request{url=url, method="POST", source=source, sink=sink, headers=headers}
  return result
end

BR,

Erwin van der Zwart


RE: what about Obix - domotiqa - 05.03.2016

cool !!
Thank for the info


RE: what about Obix - Erwin van der Zwart - 05.03.2016

(05.03.2016, 14:00)domotiqa Wrote: cool !!
Thank for the info

You will need to add this: local mpm = require('socket.http')   before using the mpm.request. The request is made to a basic auhtentication server.

I have a mpm.digest libary if you need digest login.

Have fun with it (;

BR,

Erwin van der Zwart


RE: what about Obix - edgars - 07.03.2016

good job, Erwin! Smile


RE: what about Obix - edgars - 08.03.2016

thanks to Erwin van der Zwart, I am glad to post a complete oBIX module for  integration with SmartStruxure Lite including Digest Authentication user library.

1. oBIX to MPM with digest - use as event or resident based script


Code:
--======================================================================================================
--******************************** MPM OBIX CONNECTION WITH DIGEST SUPPORT ***************************--
--************************ Version 1.0 Created by Erwin van der Zwart 08-01-2016 *********************--
--======================================================================================================

require('user.mpm_functions') -- See this library for mpm number(s) with valid credentials and IP settings

-- Select MPM (mpm number, autoresolve node id)
mpm_settings = get_mpm_settings(1, true)

-- get data from mpm (mpm settings, object, item)
result = get_data_from_mpm(mpm_settings, "AV61", "Present_Value")
log(result)

-- post data to mpm (mpm number, object, item, value)
post_data_to_mpm(mpm_settings, "AV61", "Present_Value", 111)

-- get data from mpm (mpm settings, object, item)
result = get_data_from_mpm(mpm_settings, "AV62", "Present_Value")
log(result)

-- post data to mpm (mpm number, object, item, value)
post_data_to_mpm(mpm_settings, "AV62", "Present_Value", 222)


 2. user.mpm_functions for oBIX to MPM with digest - user libary 1

Code:
--======================================================================================================
--******************************* MPM FUNCTION LIBRARY WITH DIGEST SUPPORT ***************************--
--************************ Version 1.0 Created by Erwin van der Zwart 08-01-2016 *********************--
--======================================================================================================

local mpm = require "user.mpm_digest"

function get_mpm_settings(mpm_number,autoresolve)
 
 function resolve_node()
     local url = "http://" .. mpm_username .. ":" .. mpm_password .. "@" .. mpm_ip
     local b, c, h = mpm.request(url)
     -- resolve node id automaticly (if not found we will use above but that one can be wrong)
     if h then
       mpm_node_id = string.sub(h.server, 1, 7)
     end
     return mpm_node_id
 end
 
 --======================================================================================================
 --**************************************** Put here your MPM settings ********************************--
 --======================================================================================================
 
 if mpm_number == 1 then -- Select MPM 1
   mpm_ip = '192.168.10.205'
   mpm_username = 'admin'
   mpm_password = 'Schneider'
   mpm_node_id = 'N004EAB'
   mpm_instance = 100
 elseif mpm_number == 2 then -- Select MPM 2
   mpm_ip = '192.168.10.206'
   mpm_username = 'admin'
   mpm_password = 'Schneider'
   mpm_node_id = 'N005EAB'
   mpm_instance = 150
 elseif mpm_number == 3 then -- Select MPM 3
   mpm_ip = '192.168.10.207'
   mpm_username = 'admin'
   mpm_password = 'Schneider'
   mpm_node_id = 'N006EAB'
   mpm_instance = 200
 else -- Select MPM with default settings (node ID is resolved automaticly)
   mpm_ip = '10.50.80.3'
   mpm_username = 'admin'
   mpm_password = 'admin'
   mpm_node_id =  'N000000'
   autoresolve = true
   mpm_instance = 100
 end
 
 --======================================================================================================
 --******************************************** End of MPM settings ***********************************--
 --======================================================================================================

 
 MPM_Settings = {ip = mpm_ip, username = mpm_username, password = mpm_password, node = mpm_node_id, instance = mpm_instance}
 
 if autoresolve then
     MPM_Settings.node = resolve_node()
 end

 return MPM_Settings
end

-- function to get data from mpm
function get_data_from_mpm(mpm_settings,object,item)
 local url = "http://" .. mpm_settings.username .. ":" .. mpm_settings.password .. "@" .. mpm_settings.ip .. "/obix/network/" .. mpm_settings.node .. "/DEV" .. mpm_settings.instance .. "/" .. object .. "/" .. item
 local b, c, h = mpm.request(url)
 local value = b:match([[val="(.-)"]])
 local value = tonumber(value)
 return value
end

-- function to post data to mpm
function post_data_to_mpm(mpm_settings,object,item,value)
 local url = "http://" .. mpm_settings.username .. ":" .. mpm_settings.password .. "@" .. mpm_settings.ip .. "/obix/network/" .. mpm_settings.node .. "/DEV" .. mpm_settings.instance .. "/" .. object .. "/" .. item .. "/"
 local reqBody = [[<intl val="]] .. value .. [["/>]]
 local headers = {["Content-Type"] = "text/xml", ["Content-Length"] = #reqBody}
 local respTable = {}
 local returnList = {client = {}, code = {}, headers = {}, status = {}}
 local source=ltn12.source.string(reqBody)
 local sink=ltn12.sink.table(respTable)
 local result = mpm.request{url=url, method="POST", source=source, sink=sink, headers=headers}
 return result
end

3. user.mpm_digest for oBIX to MPM with digest - user libary 2

Code:
--======================================================================================================
--******************************* MPM DIGEST LIBRARY FOR DIGEST MD5 SUPPORT **************************--
--************************ Version 1.0 Created by Erwin van der Zwart 08-01-2016 *********************--
--======================================================================================================

local md5sum = nil

local md5 = {}

local char, byte, format, rep, sub =
 string.char, string.byte, string.format, string.rep, string.sub
local bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift

local ok, bit = pcall(require, 'bit')
if ok then
 bit_or, bit_and, bit_not, bit_xor, bit_rshift, bit_lshift = bit.bor, bit.band, bit.bnot, bit.bxor, bit.rshift, bit.lshift
else
 ok, bit = pcall(require, 'bit32')

 if ok then

   bit_not = bit.bnot

   local tobit = function(n)
     return n <= 0x7fffffff and n or -(bit_not(n) + 1)
   end

   local normalize = function(f)
     return function(a,b) return tobit(f(tobit(a), tobit(b))) end
   end

   bit_or, bit_and, bit_xor = normalize(bit.bor), normalize(bit.band), normalize(bit.bxor)
   bit_rshift, bit_lshift = normalize(bit.rshift), normalize(bit.lshift)

 else

   local function tbl2number(tbl)
     local result = 0
     local power = 1
     for i = 1, #tbl do
       result = result + tbl[i] * power
       power = power * 2
     end
     return result
   end

   local function expand(t1, t2)
     local big, small = t1, t2
     if(#big < #small) then
       big, small = small, big
     end
     for i = #small + 1, #big do
       small[i] = 0
     end
   end

   local to_bits

   bit_not = function(n)
     local tbl = to_bits(n)
     local size = math.max(#tbl, 32)
     for i = 1, size do
       if(tbl[i] == 1) then
         tbl[i] = 0
       else
         tbl[i] = 1
       end
     end
     return tbl2number(tbl)
   end

   to_bits = function (n)
     if(n < 0) then
       return to_bits(bit_not(math.abs(n)) + 1)
     end
     local tbl = {}
     local cnt = 1
     local last
     while n > 0 do
       last      = n % 2
       tbl[cnt]  = last
       n         = (n-last)/2
       cnt       = cnt + 1
     end

     return tbl
   end

   bit_or = function(m, n)
     local tbl_m = to_bits(m)
     local tbl_n = to_bits(n)
     expand(tbl_m, tbl_n)

     local tbl = {}
     for i = 1, #tbl_m do
       if(tbl_m[i]== 0 and tbl_n[i] == 0) then
         tbl[i] = 0
       else
         tbl[i] = 1
       end
     end

     return tbl2number(tbl)
   end

   bit_and = function(m, n)
     local tbl_m = to_bits(m)
     local tbl_n = to_bits(n)
     expand(tbl_m, tbl_n)

     local tbl = {}
     for i = 1, #tbl_m do
       if(tbl_m[i]== 0 or tbl_n[i] == 0) then
         tbl[i] = 0
       else
         tbl[i] = 1
       end
     end

     return tbl2number(tbl)
   end

   bit_xor = function(m, n)
     local tbl_m = to_bits(m)
     local tbl_n = to_bits(n)
     expand(tbl_m, tbl_n)

     local tbl = {}
     for i = 1, #tbl_m do
       if(tbl_m[i] ~= tbl_n[i]) then
         tbl[i] = 1
       else
         tbl[i] = 0
       end
     end

     return tbl2number(tbl)
   end

   bit_rshift = function(n, bits)
     local high_bit = 0
     if(n < 0) then
       n = bit_not(math.abs(n)) + 1
       high_bit = 0x80000000
     end

     local floor = math.floor

     for i=1, bits do
       n = n/2
       n = bit_or(floor(n), high_bit)
     end
     return floor(n)
   end

   bit_lshift = function(n, bits)
     if(n < 0) then
       n = bit_not(math.abs(n)) + 1
     end

     for i=1, bits do
       n = n*2
     end
     return bit_and(n, 0xFFFFFFFF)
   end
 end
end

local function lei2str(i)
 local f=function (s) return char( bit_and( bit_rshift(i, s), 255)) end
 return f(0)..f(8)..f(16)..f(24)
end

local function str2bei(s)
 local v=0
 for i=1, #s do
   v = v * 256 + byte(s, i)
 end
 return v
end

local function str2lei(s)
 local v=0
 for i = #s,1,-1 do
   v = v*256 + byte(s, i)
 end
 return v
end

local function cut_le_str(s,...)
 local o, r = 1, {}
 local args = {...}
 for i=1, #args do
   table.insert(r, str2lei(sub(s, o, o + args[i] - 1)))
   o = o + args[i]
 end
 return r
end

local swap = function (w) return str2bei(lei2str(w)) end

local function hex2binaryaux(hexval)
 return char(tonumber(hexval, 16))
end

local function hex2binary(hex)
 local result, _ = hex:gsub('..', hex2binaryaux)
 return result
end

local CONSTS = {
 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
}

local f=function (x,y,z) return bit_or(bit_and(x,y),bit_and(-x-1,z)) end
local g=function (x,y,z) return bit_or(bit_and(x,z),bit_and(y,-z-1)) end
local h=function (x,y,z) return bit_xor(x,bit_xor(y,z)) end
local i=function (x,y,z) return bit_xor(y,bit_or(x,-z-1)) end
local z=function (f,a,b,c,d,x,s,ac)
 a=bit_and(a+f(b,c,d)+x+ac,0xFFFFFFFF)
 return bit_or(bit_lshift(bit_and(a,bit_rshift(0xFFFFFFFF,s)),s),bit_rshift(a,32-s))+b
end

local function transform(A,B,C,D,X)
 local a,b,c,d=A,B,C,D
 local t=CONSTS

 a=z(f,a,b,c,d,X[ 0], 7,t[ 1])
 d=z(f,d,a,b,c,X[ 1],12,t[ 2])
 c=z(f,c,d,a,b,X[ 2],17,t[ 3])
 b=z(f,b,c,d,a,X[ 3],22,t[ 4])
 a=z(f,a,b,c,d,X[ 4], 7,t[ 5])
 d=z(f,d,a,b,c,X[ 5],12,t[ 6])
 c=z(f,c,d,a,b,X[ 6],17,t[ 7])
 b=z(f,b,c,d,a,X[ 7],22,t[ 8])
 a=z(f,a,b,c,d,X[ 8], 7,t[ 9])
 d=z(f,d,a,b,c,X[ 9],12,t[10])
 c=z(f,c,d,a,b,X[10],17,t[11])
 b=z(f,b,c,d,a,X[11],22,t[12])
 a=z(f,a,b,c,d,X[12], 7,t[13])
 d=z(f,d,a,b,c,X[13],12,t[14])
 c=z(f,c,d,a,b,X[14],17,t[15])
 b=z(f,b,c,d,a,X[15],22,t[16])

 a=z(g,a,b,c,d,X[ 1], 5,t[17])
 d=z(g,d,a,b,c,X[ 6], 9,t[18])
 c=z(g,c,d,a,b,X[11],14,t[19])
 b=z(g,b,c,d,a,X[ 0],20,t[20])
 a=z(g,a,b,c,d,X[ 5], 5,t[21])
 d=z(g,d,a,b,c,X[10], 9,t[22])
 c=z(g,c,d,a,b,X[15],14,t[23])
 b=z(g,b,c,d,a,X[ 4],20,t[24])
 a=z(g,a,b,c,d,X[ 9], 5,t[25])
 d=z(g,d,a,b,c,X[14], 9,t[26])
 c=z(g,c,d,a,b,X[ 3],14,t[27])
 b=z(g,b,c,d,a,X[ 8],20,t[28])
 a=z(g,a,b,c,d,X[13], 5,t[29])
 d=z(g,d,a,b,c,X[ 2], 9,t[30])
 c=z(g,c,d,a,b,X[ 7],14,t[31])
 b=z(g,b,c,d,a,X[12],20,t[32])

 a=z(h,a,b,c,d,X[ 5], 4,t[33])
 d=z(h,d,a,b,c,X[ 8],11,t[34])
 c=z(h,c,d,a,b,X[11],16,t[35])
 b=z(h,b,c,d,a,X[14],23,t[36])
 a=z(h,a,b,c,d,X[ 1], 4,t[37])
 d=z(h,d,a,b,c,X[ 4],11,t[38])
 c=z(h,c,d,a,b,X[ 7],16,t[39])
 b=z(h,b,c,d,a,X[10],23,t[40])
 a=z(h,a,b,c,d,X[13], 4,t[41])
 d=z(h,d,a,b,c,X[ 0],11,t[42])
 c=z(h,c,d,a,b,X[ 3],16,t[43])
 b=z(h,b,c,d,a,X[ 6],23,t[44])
 a=z(h,a,b,c,d,X[ 9], 4,t[45])
 d=z(h,d,a,b,c,X[12],11,t[46])
 c=z(h,c,d,a,b,X[15],16,t[47])
 b=z(h,b,c,d,a,X[ 2],23,t[48])

 a=z(i,a,b,c,d,X[ 0], 6,t[49])
 d=z(i,d,a,b,c,X[ 7],10,t[50])
 c=z(i,c,d,a,b,X[14],15,t[51])
 b=z(i,b,c,d,a,X[ 5],21,t[52])
 a=z(i,a,b,c,d,X[12], 6,t[53])
 d=z(i,d,a,b,c,X[ 3],10,t[54])
 c=z(i,c,d,a,b,X[10],15,t[55])
 b=z(i,b,c,d,a,X[ 1],21,t[56])
 a=z(i,a,b,c,d,X[ 8], 6,t[57])
 d=z(i,d,a,b,c,X[15],10,t[58])
 c=z(i,c,d,a,b,X[ 6],15,t[59])
 b=z(i,b,c,d,a,X[13],21,t[60])
 a=z(i,a,b,c,d,X[ 4], 6,t[61])
 d=z(i,d,a,b,c,X[11],10,t[62])
 c=z(i,c,d,a,b,X[ 2],15,t[63])
 b=z(i,b,c,d,a,X[ 9],21,t[64])

 return A+a,B+b,C+c,D+d
end

function md5.sumhexa(s)
 local msgLen = #s
 local padLen = 56 - msgLen % 64

 if msgLen % 64 > 56 then padLen = padLen + 64 end

 if padLen == 0 then padLen = 64 end

 s = s .. char(128) .. rep(char(0),padLen-1) .. lei2str(8*msgLen) .. lei2str(0)

 assert(#s % 64 == 0)

 local t = CONSTS
 local a,b,c,d = t[65],t[66],t[67],t[68]

 for i=1,#s,64 do
   local X = cut_le_str(sub(s,i,i+63),4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4)
   assert(#X == 16)
   X[0] = table.remove(X,1)
   a,b,c,d = transform(a,b,c,d,X)
 end

 return format("%08x%08x%08x%08x",swap(a),swap(b),swap(c),swap(d))
end

function md5.sum(s)
 return hex2binary(md5.sumhexa(s))
end

do -- select MD5 library

 local ok, mod = pcall(require, "crypto")
 if ok then
   local digest = (mod.evp or mod).digest
   if digest then
     md5sum = function(str) return digest("md5", str) end
   end
 end

 if not md5sum then
     local md5 = (type(mod) == "table") and mod or md5
     md5sum = md5.sumhexa or md5.digest
 end

 if not md5sum then
   ok = pcall(require, "digest") -- last because using globals
   if ok and md5 then md5sum = md5.digest end
 end

end

local s_http = require "socket.http"
local s_url = require "socket.url"
local ltn12 = require "ltn12"

local hash = function(...)
 return md5sum(table.concat({...}, ":"))
end

local parse_header = function(h)
 local r = {}
 for k,v in (h .. ','):gmatch("(%w+)=(.-),") do
   if v:sub(1, 1) == '"' then -- strip quotes
     r[k:lower()] = v:sub(2, -2)
   else r[k:lower()] = v end
 end
 return r
end

local make_digest_header = function(t)
 local s = {}
 local x
 for i=1,#t do
   x = t[i]
   if x.unquote then
     s[i] =  x[1] .. '=' .. x[2]
   else
     s[i] = x[1] .. '="' .. x[2] .. '"'
   end
 end
 return "Digest " .. table.concat(s, ', ')
end

local hcopy = function(t)
 local r = {}
 for k,v in pairs(t) do r[k] = v end
 return r
end

local _request = function(t)
 if not t.url then error("missing URL") end
 local url = s_url.parse(t.url)
 local user, password = url.user, url.password
 if not (user and password) then
   error("missing credentials in URL")
 end
 url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil
 t.url = s_url.build(url)
 local ghost_source
 if t.source then
   local ghost_chunks = {}
   local ghost_capture = function(x)
     if x then ghost_chunks[#ghost_chunks+1] = x end
     return x
   end
   local ghost_i = 0
   ghost_source = function()
     ghost_i = ghost_i+1
     return ghost_chunks[ghost_i]
   end
   t.source = ltn12.source.chain(t.source, ghost_capture)
 end
 local b, c, h = s_http.request(t)
 if (c == 401) and h["www-authenticate"] then
   local ht = parse_header(h["www-authenticate"])
   assert(ht.realm and ht.nonce and ht.opaque)
   if ht.qop ~= "auth" then
     return nil, string.format("unsupported qop (%s)", tostring(ht.qop))
   end
   if ht.algorithm and (ht.algorithm:lower() ~= "md5") then
     return nil, string.format("unsupported algo (%s)", tostring(ht.algorithm))
   end
   local nc, cnonce = "00000001", string.format("%08x", os.time())
   local uri = s_url.build{path = url.path, query = url.query}
   local method = t.method or "GET"
   local response = hash(
     hash(user, ht.realm, password),
     ht.nonce,
     nc,
     cnonce,
     "auth",
     hash(method, uri)
   )
   t.headers = t.headers or {}
   t.headers.authorization = make_digest_header{
     {"username", user},
     {"realm", ht.realm},
     {"nonce", ht.nonce},
     {"uri", uri},
     {"cnonce", cnonce},
     {"nc", nc, unquote=true},
     {"qop", "auth"},
     {"algorithm", "MD5"},
     {"response", response},
     {"opaque", ht.opaque},
   }
   if not t.headers.cookie and h["set-cookie"] then
     local cookie = (h["set-cookie"] .. ";"):match("(.-=.-)[;,]")
     if cookie then
       t.headers.cookie = "$Version: 0; " .. cookie .. ";"
     end
   end
   if t.source then t.source = ghost_source end
   b, c, h = s_http.request(t)
   return b, c, h
 else return b, c, h end
end

local request = function(x)
 local _t = type(x)
 if _t == "table" then
   return _request(hcopy(x))
 elseif _t == "string" then
   local r = {}
   local _, c, h = _request{url = x, sink = ltn12.sink.table(r)}
   return table.concat(r), c, h
 else error(string.format("unexpected type %s", _t)) end
end

return {
 request = request,
}