diff --git a/HISTORY.md b/HISTORY.md index 5926bc02..c5dcfadc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,12 @@ +Dev version +----------- +* skynet.exit will quit service immediately. +* Add snax.gateserver, snax.loginserver, snax.msgserver +* Simplify clientsocket lib +* mongo driver support replica set +* config file support read from ENV +* add simple httpd (see examples/simpleweb.lua) + v0.4.2 (2014-7-14) ----------- * Bugfix : invalid negative socket id diff --git a/Makefile b/Makefile index 45631905..63d9085d 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ jemalloc : $(MALLOC_STATICLIB) CSERVICE = snlua logger gate harbor LUA_CLIB = skynet socketdriver int64 bson mongo md5 netpack \ cjson clientsocket memory profile multicast \ - cluster + cluster crypt SKYNET_SRC = skynet_main.c skynet_handle.c skynet_module.c skynet_mq.c \ skynet_server.c skynet_start.c skynet_timer.c skynet_error.c \ @@ -110,6 +110,9 @@ $(LUA_CLIB_PATH)/multicast.so : lualib-src/lua-multicast.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/cluster.so : lualib-src/lua-cluster.c | $(LUA_CLIB_PATH) $(CC) $(CFLAGS) $(SHARED) -Iskynet-src $^ -o $@ +$(LUA_CLIB_PATH)/crypt.so : lualib-src/lua-crypt.c | $(LUA_CLIB_PATH) + $(CC) $(CFLAGS) $(SHARED) $^ -o $@ + clean : rm -f $(SKYNET_BUILD_PATH)/skynet $(CSERVICE_PATH)/*.so $(LUA_CLIB_PATH)/*.so diff --git a/examples/client.lua b/examples/client.lua index 278922e6..14342556 100644 --- a/examples/client.lua +++ b/examples/client.lua @@ -2,30 +2,46 @@ package.cpath = "luaclib/?.so" local socket = require "clientsocket" local cjson = require "cjson" +local bit32 = require "bit32" -local fd = socket.connect("127.0.0.1", 8888) +local fd = assert(socket.connect("127.0.0.1", 8888)) -local last -local result = {} +local function send_package(fd, pack) + local size = #pack + local package = string.char(bit32.extract(size,8,8)) .. + string.char(bit32.extract(size,0,8)).. + pack -local function dispatch() - while true do - local status - status, last = socket.recv(fd, last, result) - if status == nil then - error "Server closed" - end - if not status then - break - end - for _, v in ipairs(result) do - local session,t,str = string.match(v, "(%d+)(.)(.*)") - assert(t == '-' or t == '+') - session = tonumber(session) - local result = cjson.decode(str) - print("Response:",session, result[1], result[2]) - end + socket.send(fd, package) +end + +local function unpack_package(text) + local size = #text + if size < 2 then + return nil, text end + local s = text:byte(1) * 256 + text:byte(2) + if size < s+2 then + return nil, text + end + + return text:sub(3,2+s), text:sub(3+s) +end + +local function recv_package(last) + local result + result, last = unpack_package(last) + if result then + return result, last + end + local r = socket.recv(fd) + if not r then + return nil, last + end + if r == "" then + error "Server closed" + end + return unpack_package(last .. r) end local session = 0 @@ -33,12 +49,25 @@ local session = 0 local function send_request(v) session = session + 1 local str = string.format("%d+%s",session, cjson.encode(v)) - socket.send(fd, str) + send_package(fd, str) print("Request:", session) end +local last = "" + while true do - dispatch() + while true do + local v + v, last = recv_package(last) + if not v then + break + end + local session,t,str = string.match(v, "(%d+)(.)(.*)") + assert(t == '-' or t == '+') + session = tonumber(session) + local result = cjson.decode(str) + print("Response:",session, result[1], result[2]) + end local cmd = socket.readstdin() if cmd then local args = {} diff --git a/examples/config.login b/examples/config.login new file mode 100644 index 00000000..c72dcb27 --- /dev/null +++ b/examples/config.login @@ -0,0 +1,8 @@ +thread = 8 +logger = nil +harbor = 0 +start = "main" +bootstrap = "snlua bootstrap" -- The service for bootstrap +luaservice = "./service/?.lua;./examples/login/?.lua" +lualoader = "lualib/loader.lua" +cpath = "./cservice/?.so" diff --git a/examples/login/client.lua b/examples/login/client.lua new file mode 100644 index 00000000..2dfdf25b --- /dev/null +++ b/examples/login/client.lua @@ -0,0 +1,184 @@ +package.cpath = "luaclib/?.so" + +local socket = require "clientsocket" +local crypt = require "crypt" +local bit32 = require "bit32" + +local fd = assert(socket.connect("127.0.0.1", 8001)) + +local function writeline(fd, text) + socket.send(fd, text .. "\n") +end + +local function unpack_line(text) + local from = text:find("\n", 1, true) + if from then + return text:sub(1, from-1), text:sub(from+1) + end + return nil, text +end + +local last = "" + +local function unpack_f(f) + local function try_recv(fd, last) + local result + result, last = f(last) + if result then + return result, last + end + local r = socket.recv(fd) + if not r then + return nil, last + end + if r == "" then + error "Server closed" + end + return f(last .. r) + end + + return function() + while true do + local result + result, last = try_recv(fd, last) + if result then + return result + end + socket.usleep(100) + end + end +end + +local readline = unpack_f(unpack_line) + +local challenge = crypt.base64decode(readline()) + +local clientkey = crypt.randomkey() +writeline(fd, crypt.base64encode(crypt.dhexchange(clientkey))) +local secret = crypt.dhsecret(crypt.base64decode(readline()), clientkey) + +print("sceret is ", crypt.hexencode(secret)) + +local hmac = crypt.hmac64(challenge, secret) +writeline(fd, crypt.base64encode(hmac)) + +local token = { + server = "sample", + user = "hello", + pass = "password", +} + +local function encode_token(token) + return string.format("%s@%s:%s", + crypt.base64encode(token.user), + crypt.base64encode(token.server), + crypt.base64encode(token.pass)) +end + +local etoken = crypt.desencode(secret, encode_token(token)) +local b = crypt.base64encode(etoken) +writeline(fd, crypt.base64encode(etoken)) + +local result = readline() +print(result) +local code = tonumber(string.sub(result, 1, 3)) +assert(code == 200) +socket.close(fd) + +local subid = crypt.base64decode(string.sub(result, 5)) + +print("login ok, subid=", subid) + +----- connect to game server + +local function send_request(v, session) + local size = #v + 4 + local package = string.char(bit32.extract(size,8,8)).. + string.char(bit32.extract(size,0,8)).. + v.. + string.char(bit32.extract(session,24,8)).. + string.char(bit32.extract(session,16,8)).. + string.char(bit32.extract(session,8,8)).. + string.char(bit32.extract(session,0,8)) + + socket.send(fd, package) + return v, session +end + +local function recv_response(v) + local content = v:sub(1,-6) + local ok = v:sub(-5,-5):byte() + local session = 0 + for i=-4,-1 do + local c = v:byte(i) + session = session + bit32.lshift(c,(-1-i) * 8) + end + return ok ~=0 , content, session +end + +local function unpack_package(text) + local size = #text + if size < 2 then + return nil, text + end + local s = text:byte(1) * 256 + text:byte(2) + if size < s+2 then + return nil, text + end + + return text:sub(3,2+s), text:sub(3+s) +end + +local readpackage = unpack_f(unpack_package) + +local function send_package(fd, pack) + local size = #pack + local package = string.char(bit32.extract(size,8,8)).. + string.char(bit32.extract(size,0,8)).. + pack + + socket.send(fd, package) +end + +local text = "echo" +local index = 1 + +print("connect") +local fd = assert(socket.connect("127.0.0.1", 8888)) +last = "" + +local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) +local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + + +send_package(fd, handshake .. ":" .. crypt.base64encode(hmac)) + +print(readpackage()) +print("===>",send_request(text,0)) +-- don't recv response +-- print("<===",recv_response(readpackage())) + +print("disconnect") +socket.close(fd) + +index = index + 1 + +print("connect again") +local fd = assert(socket.connect("127.0.0.1", 8888)) +last = "" + +local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) +local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + +send_package(fd, handshake .. ":" .. crypt.base64encode(hmac)) + +print(readpackage()) +print("===>",send_request("fake",0)) -- request again (use last session 0, so the request message is fake) +print("===>",send_request("again",1)) -- request again (use new session) +print("<===",recv_response(readpackage())) +print("<===",recv_response(readpackage())) + + +print("disconnect") +socket.close(fd) + diff --git a/examples/login/gated.lua b/examples/login/gated.lua new file mode 100644 index 00000000..aeaa2013 --- /dev/null +++ b/examples/login/gated.lua @@ -0,0 +1,88 @@ +local msgserver = require "snax.msgserver" +local crypt = require "crypt" +local skynet = require "skynet" + +local loginservice = tonumber(...) + +local server = {} +local users = {} +local username_map = {} +local internal_id = 0 + +-- login server disallow multi login, so login_handler never be reentry +-- call by login server +function server.login_handler(uid, secret) + if users[uid] then + error(string.format("%s is already login", uid)) + end + + internal_id = internal_id + 1 + local username = msgserver.username(uid, internal_id, servername) + + -- you can use a pool to alloc new agent + local agent = skynet.newservice "msgagent" + local u = { + username = username, + agent = agent, + uid = uid, + subid = internal_id, + } + + -- trash subid (no used) + skynet.call(agent, "lua", "login", uid, internal_id, secret) + + users[uid] = u + username_map[username] = u + + msgserver.login(username, secret) + + -- you should return unique subid + return internal_id +end + +-- call by agent +function server.logout_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + msgserver.logout(u.username) + users[uid] = nil + username_map[u.username] = nil + skynet.call(loginservice, "lua", "logout",uid, subid) + end +end + +-- call by login server +function server.kick_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + -- NOTICE: logout may call skynet.exit, so you should use pcall. + pcall(skynet.call, u.agent, "lua", "logout") + end +end + +-- call by self (when socket disconnect) +function server.disconnect_handler(username) + local u = username_map[username] + if u then + skynet.call(u.agent, "lua", "afk") + end +end + +-- call by self (when recv a request from client) +function server.request_handler(username, msg, sz) + local u = username_map[username] + return skynet.tostring(skynet.rawcall(u.agent, "client", msg, sz)) +end + +-- call by self (when gate open) +function server.register_handler(name) + servername = name + skynet.call(loginservice, "lua", "register_gate", servername, skynet.self()) +end + +msgserver.start(server) + diff --git a/examples/login/logind.lua b/examples/login/logind.lua new file mode 100644 index 00000000..e6e4c10f --- /dev/null +++ b/examples/login/logind.lua @@ -0,0 +1,62 @@ +local login = require "snax.loginserver" +local crypt = require "crypt" +local skynet = require "skynet" + +local server = { + host = "127.0.0.1", + port = 8001, + multilogin = false, -- disallow multilogin + name = "login_master", +} + +local server_list = {} +local user_online = {} +local user_login = {} + +function server.auth_handler(token) + -- the token is base64(user)@base64(server):base64(password) + local user, server, password = token:match("([^@]+)@([^:]+):(.+)") + user = crypt.base64decode(user) + server = crypt.base64decode(server) + password = crypt.base64decode(password) + assert(password == "password") + return server, user +end + +function server.login_handler(server, uid, secret) + print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) + local gameserver = assert(server_list[server], "Unknown server") + -- only one can login, because disallow multilogin + local last = user_online[uid] + if last then + skynet.call(last.address, "lua", "kick", uid, last.subid) + end + if user_online[uid] then + error(string.format("user %s is already online", uid)) + end + + local subid = tostring(skynet.call(gameserver, "lua", "login", uid, secret)) + user_online[uid] = { address = gameserver, subid = subid , server = server} + return subid +end + +local CMD = {} + +function CMD.register_gate(server, address) + server_list[server] = address +end + +function CMD.logout(uid, subid) + local u = user_online[uid] + if u then + print(string.format("%s@%s is logout", uid, u.server)) + user_online[uid] = nil + end +end + +function server.command_handler(command, source, ...) + local f = assert(CMD[command]) + return f(source, ...) +end + +login(server) diff --git a/examples/login/main.lua b/examples/login/main.lua new file mode 100644 index 00000000..1eef6f4d --- /dev/null +++ b/examples/login/main.lua @@ -0,0 +1,12 @@ +local skynet = require "skynet" + +skynet.start(function() + local loginserver = skynet.newservice("logind") + local gate = skynet.newservice("gated", loginserver) + + skynet.call(gate, "lua", "open" , { + port = 8888, + maxclient = 64, + servername = "sample", + }) +end) diff --git a/examples/login/msgagent.lua b/examples/login/msgagent.lua new file mode 100644 index 00000000..fc3a2d5b --- /dev/null +++ b/examples/login/msgagent.lua @@ -0,0 +1,53 @@ +local skynet = require "skynet" + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, + unpack = skynet.tostring, +} + +local gate +local userid, subid + +local CMD = {} + +function CMD.login(source, uid, sid, secret) + -- you may use secret to make a encrypted data stream + skynet.error(string.format("%s is login", uid)) + gate = source + userid = uid + subid = sid + -- you may load user data from database +end + +local function logout() + if gate then + skynet.call(gate, "lua", "logout", userid, subid) + end + skynet.exit() +end + +function CMD.logout(source) + -- NOTICE: The logout MAY be reentry + skynet.error(string.format("%s is logout", userid)) + logout() +end + +function CMD.afk(source) + -- the connection is broken, but the user may back + skynet.error(string.format("AFK")) +end + +skynet.start(function() + -- If you want to fork a work thread , you MUST do it in CMD.login + skynet.dispatch("lua", function(session, source, command, ...) + local f = assert(CMD[command]) + skynet.ret(skynet.pack(f(source, ...))) + end) + + skynet.dispatch("client", function(_,_, msg) + -- the simple ehco service + skynet.sleep(10) -- sleep a while + skynet.ret(msg) + end) +end) diff --git a/examples/main.lua b/examples/main.lua index abbd2751..4a98d2eb 100644 --- a/examples/main.lua +++ b/examples/main.lua @@ -11,6 +11,7 @@ skynet.start(function() skynet.call(watchdog, "lua", "start", { port = 8888, maxclient = max_client, + nodelay = true, }) print("Watchdog listen on ", 8888) diff --git a/examples/simpledb.lua b/examples/simpledb.lua index a11fd8c5..3a7649e6 100644 --- a/examples/simpledb.lua +++ b/examples/simpledb.lua @@ -16,7 +16,11 @@ end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) local f = command[string.upper(cmd)] - skynet.ret(skynet.pack(f(...))) + if f then + skynet.ret(skynet.pack(f(...))) + else + error(string.format("Unknown command %s", tostring(cmd))) + end end) skynet.register "SIMPLEDB" end) diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua new file mode 100644 index 00000000..19870aba --- /dev/null +++ b/examples/simpleweb.lua @@ -0,0 +1,74 @@ +local skynet = require "skynet" +local socket = require "socket" +local httpd = require "http.httpd" +local sockethelper = require "http.sockethelper" +local urllib = require "http.url" +local table = table +local string = string + +local mode = ... + +if mode == "agent" then + +local function response(id, ...) + local ok, err = httpd.write_response(sockethelper.writefunc(id), ...) + if not ok then + -- if err == sockethelper.socket_error , that means socket closed. + skynet.error(string.format("fd = %d, %s", id, err)) + end +end + +skynet.start(function() + skynet.dispatch("lua", function (_,_,id) + socket.start(id) + -- limit request body size to 8192 (you can pass nil to unlimit) + local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192) + if code then + if code ~= 200 then + response(id, code) + else + local tmp = {} + if header.host then + table.insert(tmp, string.format("host: %s", header.host)) + end + local path, query = urllib.parse(url) + table.insert(tmp, string.format("path: %s", path)) + if query then + local q = urllib.parse_query(query) + for k, v in pairs(q) do + table.insert(tmp, string.format("query: %s= %s", k,v)) + end + end + response(id, code, table.concat(tmp,"\n")) + end + else + if url == sockethelper.socket_error then + skynet.error("socket closed") + else + skynet.error(url) + end + end + socket.close(id) + end) +end) + +else + +skynet.start(function() + local agent = {} + for i= 1, 20 do + agent[i] = skynet.newservice(SERVICE_NAME, "agent") + end + local balance = 1 + local id = socket.listen("0.0.0.0", 8001) + socket.start(id , function(id, addr) + skynet.error(string.format("%s connected, pass it to agent :%08x", addr, agent[balance])) + skynet.send(agent[balance], "lua", id) + balance = balance + 1 + if balance > #agent then + balance = 1 + end + end) +end) + +end \ No newline at end of file diff --git a/examples/watchdog.lua b/examples/watchdog.lua index e28ab9fb..8ead2253 100644 --- a/examples/watchdog.lua +++ b/examples/watchdog.lua @@ -33,7 +33,6 @@ function SOCKET.data(fd, msg) end function CMD.start(conf) - skynet.call(gate, "lua", "nodelay", true) skynet.call(gate, "lua", "open" , conf) end diff --git a/lualib-src/lua-bson.c b/lualib-src/lua-bson.c index 10e3d73a..e4dd4ab7 100644 --- a/lualib-src/lua-bson.c +++ b/lualib-src/lua-bson.c @@ -1180,7 +1180,6 @@ luaopen_bson(lua_State *L) { { "timestamp", ltimestamp }, { "regex", lregex }, { "binary", lbinary }, - { "regex", lregex }, { "objectid", lobjectid }, { "decode", ldecode }, { NULL, NULL }, diff --git a/lualib-src/lua-clientsocket.c b/lualib-src/lua-clientsocket.c index 2676c267..89bfe1f8 100644 --- a/lualib-src/lua-clientsocket.c +++ b/lualib-src/lua-clientsocket.c @@ -75,47 +75,12 @@ lsend(lua_State *L) { size_t sz = 0; int fd = luaL_checkinteger(L,1); const char * msg = luaL_checklstring(L, 2, &sz); - uint8_t tmp[sz + 2]; - if (sz >= 0x10000) { - return luaL_error(L, "package too long %d (16bit limited)", (int)sz); - } - tmp[0] = (sz >> 8) & 0xff; - tmp[1] = sz & 0xff; - memcpy(tmp+2, msg, sz); - block_send(L, fd, (const char *)tmp, (int)sz+2); + block_send(L, fd, msg, (int)sz); return 0; } - -static int -unpack(lua_State *L, uint8_t *buffer, int sz, int n) { - int size = 0; - if (sz >= 2) { - size = buffer[0] << 8 | buffer[1]; - if (size > sz - 2) { - goto _block; - } - } else { - goto _block; - } - ++n; - lua_pushlstring(L, (const char *)buffer+2, size); - lua_rawseti(L, 3, n); - buffer += size + 2; - sz -= size + 2; - return unpack(L, buffer, sz, n); -_block: - lua_pushboolean(L, n==0 ? 0:1); - if (sz == 0) { - lua_pushnil(L); - } else { - lua_pushlstring(L, (const char *)buffer, sz); - } - return 2; -} - /* intger fd string last @@ -132,100 +97,26 @@ struct socket_buffer { }; static int -recv_socket(lua_State *L, char *tmp, struct socket_buffer *result) { +lrecv(lua_State *L) { int fd = luaL_checkinteger(L,1); - size_t sz = 0; - const char * last = lua_tolstring(L,2,&sz); - luaL_checktype(L, 3, LUA_TTABLE); - char * buffer; - int r = recv(fd, tmp, CACHE_SIZE, 0); + char buffer[CACHE_SIZE]; + int r = recv(fd, buffer, CACHE_SIZE, 0); if (r == 0) { + lua_pushliteral(L, ""); // close - return 0; + return 1; } if (r < 0) { if (errno == EAGAIN || errno == EINTR) { - lua_pushboolean(L, 0); - lua_pushvalue(L, 2); - return 2; + return 0; } luaL_error(L, "socket error: %s", strerror(errno)); } - if (sz + r <= CACHE_SIZE) { - buffer = tmp; - memmove(buffer + sz, buffer, r); - memcpy(buffer, last, sz); - } else { - buffer = lua_newuserdata(L, r + sz); - memcpy(buffer, last, sz); - memcpy(buffer + sz, tmp, r); - } - - int i; - int n = lua_rawlen(L, 3); - for (i=1;i<=n;i++) { - lua_pushnil(L); - lua_rawseti(L, 3, i); - } - result->buffer = buffer; - result->sz = r + sz; - return -1; + lua_pushlstring(L, buffer, r); + return 1; } -static int -lrecv(lua_State *L) { - struct socket_buffer sb; - char tmp[CACHE_SIZE]; - int ret = recv_socket(L, tmp, &sb); - if (ret < 0) { - return unpack(L, sb.buffer, sb.sz, 0); - } else { - return ret; - } -} - -static int -unpack_line(lua_State *L, uint8_t *buffer, int sz, int n) { - if (sz == 0) - goto _block; - if (buffer[0] == '\n') { - return unpack_line(L, buffer+1, sz-1, n); - } - int i; - for (i=1;i +#include + +#include +#include +#include +#include + +#define SMALL_CHUNK 256 + +/* the eight DES S-boxes */ + +uint32_t SB1[64] = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 +}; + +static uint32_t SB2[64] = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 +}; + +static uint32_t SB3[64] = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 +}; + +static uint32_t SB4[64] = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 +}; + +static uint32_t SB5[64] = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 +}; + +static uint32_t SB6[64] = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 +}; + +static uint32_t SB7[64] = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 +}; + +static uint32_t SB8[64] = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 +}; + +/* PC1: left and right halves bit-swap */ + +static uint32_t LHs[16] = { + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static uint32_t RHs[16] = { + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + +/* platform-independant 32-bit integer manipulation macros */ + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8_t) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uint8_t) ( (n) ); \ +} + +/* Initial Permutation macro */ + +#define DES_IP(X,Y) \ +{ \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \ + X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \ +} + +/* Final Permutation macro */ + +#define DES_FP(X,Y) \ +{ \ + X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \ + Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ +} + +/* DES round macro */ + +#define DES_ROUND(X,Y) \ +{ \ + T = *SK++ ^ X; \ + Y ^= SB8[ (T ) & 0x3F ] ^ \ + SB6[ (T >> 8) & 0x3F ] ^ \ + SB4[ (T >> 16) & 0x3F ] ^ \ + SB2[ (T >> 24) & 0x3F ]; \ + \ + T = *SK++ ^ ((X << 28) | (X >> 4)); \ + Y ^= SB7[ (T ) & 0x3F ] ^ \ + SB5[ (T >> 8) & 0x3F ] ^ \ + SB3[ (T >> 16) & 0x3F ] ^ \ + SB1[ (T >> 24) & 0x3F ]; \ +} + +/* DES key schedule */ + +static void +des_main_ks( uint32_t SK[32], const uint8_t key[8] ) { + int i; + uint32_t X, Y, T; + + GET_UINT32( X, key, 0 ); + GET_UINT32( Y, key, 4 ); + + /* Permuted Choice 1 */ + + T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); + T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); + + X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) + | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) + | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) + | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); + + Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) + | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) + | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) + | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); + + X &= 0x0FFFFFFF; + Y &= 0x0FFFFFFF; + + /* calculate subkeys */ + + for( i = 0; i < 16; i++ ) + { + if( i < 2 || i == 8 || i == 15 ) + { + X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; + Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; + } + else + { + X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; + Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) + | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) + | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) + | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) + | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) + | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) + | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) + | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) + | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) + | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) + | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); + + *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) + | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) + | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) + | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) + | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) + | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) + | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) + | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) + | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) + | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) + | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); + } +} + +/* DES 64-bit block encryption/decryption */ + +static void +des_crypt( const uint32_t SK[32], const uint8_t input[8], uint8_t output[8] ) { + uint32_t X, Y, T; + + GET_UINT32( X, input, 0 ); + GET_UINT32( Y, input, 4 ); + + DES_IP( X, Y ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_FP( Y, X ); + + PUT_UINT32( Y, output, 0 ); + PUT_UINT32( X, output, 4 ); +} + +static int +lrandomkey(lua_State *L) { + char tmp[8]; + int i; + for (i=0;i<8;i++) { + tmp[i] = random() & 0xff; + } + lua_pushlstring(L, tmp, 8); + return 1; +} + +static void +des_key(lua_State *L, uint32_t SK[32]) { + size_t keysz = 0; + const void * key = luaL_checklstring(L, 1, &keysz); + if (keysz != 8) { + luaL_error(L, "Invalid key size %d, need 8 bytes", (int)keysz); + } + des_main_ks(SK, key); +} + +static int +ldesencode(lua_State *L) { + uint32_t SK[32]; + des_key(L, SK); + + size_t textsz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + size_t chunksz = (textsz + 8) & ~7; + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (chunksz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, chunksz); + } + int i; + for (i=0;i<(int)textsz-7;i+=8) { + des_crypt(SK, text+i, buffer+i); + } + int bytes = textsz - i; + uint8_t tail[8]; + int j; + for (j=0;j<8;j++) { + if (j < bytes) { + tail[j] = text[i+j]; + } else if (j==bytes) { + tail[j] = 0x80; + } else { + tail[j] = 0; + } + } + des_crypt(SK, tail, buffer+i); + lua_pushlstring(L, (const char *)buffer, chunksz); + + return 1; +} + +static int +ldesdecode(lua_State *L) { + uint32_t ESK[32]; + des_key(L, ESK); + uint32_t SK[32]; + int i; + for( i = 0; i < 32; i += 2 ) { + SK[i] = ESK[30 - i]; + SK[i + 1] = ESK[31 - i]; + } + size_t textsz = 0; + const uint8_t *text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + if ((textsz & 7) || textsz == 0) { + return luaL_error(L, "Invalid des crypt text length %d", (int)textsz); + } + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (textsz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, textsz); + } + for (i=0;i=textsz-8;i--) { + if (buffer[i] == 0) { + padding++; + } else if (buffer[i] == 0x80) { + break; + } else { + return luaL_error(L, "Invalid des crypt text"); + } + } + if (padding > 8) { + return luaL_error(L, "Invalid des crypt text"); + } + lua_pushlstring(L, (const char *)buffer, textsz - padding); + return 1; +} + + +static void +Hash(const char * str, int sz, uint8_t key[8]) { + uint32_t djb_hash = 5381L; + uint32_t js_hash = 1315423911L; + + int i; + for (i=0;i> 2)); + } + + key[0] = djb_hash & 0xff; + key[1] = (djb_hash >> 8) & 0xff; + key[2] = (djb_hash >> 16) & 0xff; + key[3] = (djb_hash >> 24) & 0xff; + + key[4] = js_hash & 0xff; + key[5] = (js_hash >> 8) & 0xff; + key[6] = (js_hash >> 16) & 0xff; + key[7] = (js_hash >> 24) & 0xff; +} + +static int +lhashkey(lua_State *L) { + size_t sz = 0; + const char * key = luaL_checklstring(L, 1, &sz); + uint8_t realkey[8]; + Hash(key,(int)sz,realkey); + lua_pushlstring(L, (const char *)realkey, 8); + return 1; +} + +static int +ltohex(lua_State *L) { + static char hex[] = "0123456789abcdef"; + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (sz > SMALL_CHUNK/2) { + buffer = lua_newuserdata(L, sz * 2); + } + int i; + for (i=0;i> 4]; + buffer[i*2+1] = hex[text[i] & 0xf]; + } + lua_pushlstring(L, buffer, sz * 2); + return 1; +} + +#define HEX(v,c) { char tmp = (char) c; if (tmp >= '0' && tmp <= '9') { v = tmp-'0'; } else { v = tmp - 'a' + 10; } } + +static int +lfromhex(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (sz & 2) { + return luaL_error(L, "Invalid hex text size %d", (int)sz); + } + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (sz > SMALL_CHUNK*2) { + buffer = lua_newuserdata(L, sz / 2); + } + int i; + for (i=0;i 16 || low > 16) { + return luaL_error(L, "Invalid hex text", text); + } + buffer[i/2] = hi<<4 | low; + } + lua_pushlstring(L, buffer, i/2); + return 1; +} + +// Constants are the integer part of the sines of integers (in radians) * 2^32. +const uint32_t k[64] = { +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 }; + +// r specifies the per-round shift amounts +const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +// leftrotate function definition +#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + +static void +hmac(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { + uint32_t w[16]; + uint32_t a, b, c, d, f, g, temp; + int i; + + a = 0x67452301u; + b = 0xefcdab89u; + c = 0x98badcfeu; + d = 0x10325476u; + + for (i=0;i<16;i+=4) { + w[i] = x[1]; + w[i+1] = x[0]; + w[i+2] = y[1]; + w[i+3] = y[0]; + } + + for(i = 0; i<64; i++) { + if (i < 16) { + f = (b & c) | ((~b) & d); + g = i; + } else if (i < 32) { + f = (d & b) | ((~d) & c); + g = (5*i + 1) % 16; + } else if (i < 48) { + f = b ^ c ^ d; + g = (3*i + 5) % 16; + } else { + f = c ^ (b | (~d)); + g = (7*i) % 16; + } + + temp = d; + d = c; + c = b; + b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]); + a = temp; + + } + + result[0] = c^d; + result[1] = a^b; +} + +static void +read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac x"); + } + const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac y"); + } + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24; + yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24; +} + +static int +lhmac64(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint32_t result[2]; + hmac(x,y,result); + uint8_t tmp[8]; + tmp[0] = result[0] & 0xff; + tmp[1] = (result[0] >> 8 )& 0xff; + tmp[2] = (result[0] >> 16 )& 0xff; + tmp[3] = (result[0] >> 24 )& 0xff; + tmp[4] = result[1] & 0xff; + tmp[5] = (result[1] >> 8 )& 0xff; + tmp[6] = (result[1] >> 16 )& 0xff; + tmp[7] = (result[1] >> 24 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); + return 1; +} + +// powmodp64 for DH-key exchange + +// The biggest 64bit prime +#define P 0xffffffffffffffc5ull + +static inline uint64_t +mul_mod_p(uint64_t a, uint64_t b) { + uint64_t m = 0; + while(b) { + if(b&1) { + uint64_t t = P-a; + if ( m >= t) { + m -= t; + } else { + m += a; + } + } + if (a >= P - a) { + a = a * 2 - P; + } else { + a = a * 2; + } + b>>=1; + } + return m; +} + +static inline uint64_t +pow_mod_p(uint64_t a, uint64_t b) { + if (b==1) { + return a; + } + uint64_t t = pow_mod_p(a, b>>1); + t = mul_mod_p(t,t); + if (b % 2) { + t = mul_mod_p(t, a); + } + return t; +} + +// calc a^b % p +static uint64_t +powmodp(uint64_t a, uint64_t b) { + if (a > P) + a%=P; + return pow_mod_p(a,b); +} + +static void +push64(lua_State *L, uint64_t r) { + uint8_t tmp[8]; + tmp[0] = r & 0xff; + tmp[1] = (r >> 8 )& 0xff; + tmp[2] = (r >> 16 )& 0xff; + tmp[3] = (r >> 24 )& 0xff; + tmp[4] = (r >> 32 )& 0xff; + tmp[5] = (r >> 40 )& 0xff; + tmp[6] = (r >> 48 )& 0xff; + tmp[7] = (r >> 56 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); +} + +static int +ldhsecret(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint64_t r = powmodp((uint64_t)x[0] | (uint64_t)x[1]<<32, + (uint64_t)y[0] | (uint64_t)y[1]<<32); + + push64(L, r); + + return 1; +} + +#define G 5 + +static int +ldhexchange(lua_State *L) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac x"); + } + uint32_t xx[2]; + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + + uint64_t r = powmodp(5, (uint64_t)xx[0] | (uint64_t)xx[1]<<32); + push64(L, r); + return 1; +} + +// base64 + +static int +lb64encode(lua_State *L) { + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + int encode_sz = (sz + 2)/3*4; + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (encode_sz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, encode_sz); + } + int i,j; + j=0; + for (i=0;i<(int)sz-2;i+=3) { + uint32_t v = text[i] << 16 | text[i+1] << 8 | text[i+2]; + buffer[j] = encoding[v >> 18]; + buffer[j+1] = encoding[(v >> 12) & 0x3f]; + buffer[j+2] = encoding[(v >> 6) & 0x3f]; + buffer[j+3] = encoding[(v) & 0x3f]; + j+=4; + } + int padding = sz-i; + uint32_t v; + switch(padding) { + case 1 : + v = text[i]; + buffer[j] = encoding[v >> 2]; + buffer[j+1] = encoding[(v & 3) << 4]; + buffer[j+2] = '='; + buffer[j+3] = '='; + break; + case 2 : + v = text[i] << 8 | text[i+1]; + buffer[j] = encoding[v >> 10]; + buffer[j+1] = encoding[(v >> 4) & 0x3f]; + buffer[j+2] = encoding[(v & 0xf) << 2]; + buffer[j+3] = '='; + break; + } + lua_pushlstring(L, buffer, encode_sz); + return 1; +} + +static inline int +b64index(uint8_t c) { + static const int decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + int decoding_size = sizeof(decoding)/sizeof(decoding[0]); + if (c<43) { + return -1; + } + c -= 43; + if (c>=decoding_size) + return -1; + return decoding[c]; +} + +static int +lb64decode(lua_State *L) { + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + int decode_sz = (sz+3)/4*3; + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (decode_sz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, decode_sz); + } + int i,j; + int output = 0; + for (i=0;i=sz) { + return luaL_error(L, "Invalid base64 text"); + } + c[j] = b64index(text[i]); + if (c[j] == -1) { + ++i; + continue; + } + if (c[j] == -2) { + ++padding; + } + ++i; + ++j; + } + uint32_t v; + switch (padding) { + case 0: + v = (unsigned)c[0] << 18 | c[1] << 12 | c[2] << 6 | c[3]; + buffer[output] = v >> 16; + buffer[output+1] = (v >> 8) & 0xff; + buffer[output+2] = v & 0xff; + output += 3; + break; + case 1: + if (c[3] != -2 || (c[2] & 3)!=0) { + return luaL_error(L, "Invalid base64 text"); + } + v = (unsigned)c[0] << 10 | c[1] << 4 | c[2] >> 2 ; + buffer[output] = v >> 8; + buffer[output+1] = v & 0xff; + output += 2; + break; + case 2: + if (c[3] != -2 || c[2] != -2 || (c[1] & 0xf) !=0) { + return luaL_error(L, "Invalid base64 text"); + } + v = (unsigned)c[0] << 2 | c[1] >> 4; + buffer[output] = v; + ++ output; + break; + default: + return luaL_error(L, "Invalid base64 text"); + } + } + lua_pushlstring(L, buffer, output); + return 1; +} + +int +luaopen_crypt(lua_State *L) { + luaL_checkversion(L); + srandom(time(NULL)); + luaL_Reg l[] = { + { "hashkey", lhashkey }, + { "randomkey", lrandomkey }, + { "desencode", ldesencode }, + { "desdecode", ldesdecode }, + { "hexencode", ltohex }, + { "hexdecode", lfromhex }, + { "hmac64", lhmac64 }, + { "dhexchange", ldhexchange }, + { "dhsecret", ldhsecret }, + { "base64encode", lb64encode }, + { "base64decode", lb64decode }, + { NULL, NULL }, + }; + luaL_newlib(L,l); + return 1; +} diff --git a/lualib-src/lua-netpack.c b/lualib-src/lua-netpack.c index a95ed4d7..4012c9bc 100644 --- a/lualib-src/lua-netpack.c +++ b/lualib-src/lua-netpack.c @@ -393,13 +393,13 @@ lpop(lua_State *L) { */ static const char * -tolstring(lua_State *L, size_t *sz) { +tolstring(lua_State *L, size_t *sz, int index) { const char * ptr; - if (lua_isuserdata(L,1)) { - ptr = (const char *)lua_touserdata(L,1); - *sz = (size_t)luaL_checkinteger(L, 2); + if (lua_isuserdata(L,index)) { + ptr = (const char *)lua_touserdata(L,index); + *sz = (size_t)luaL_checkinteger(L, index+1); } else { - ptr = luaL_checklstring(L, 1, sz); + ptr = luaL_checklstring(L, index, sz); } return ptr; } @@ -413,7 +413,7 @@ write_size(uint8_t * buffer, int len) { static int lpack(lua_State *L) { size_t len; - const char * ptr = tolstring(L, &len); + const char * ptr = tolstring(L, &len, 1); if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); } @@ -433,7 +433,7 @@ lpack_string(lua_State *L) { uint8_t tmp[SMALLSTRING+2]; size_t len; uint8_t *buffer; - const char * ptr = tolstring(L, &len); + const char * ptr = tolstring(L, &len, 1); if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); } @@ -451,6 +451,34 @@ lpack_string(lua_State *L) { return 1; } +static int +lpack_padding(lua_State *L) { + uint8_t tmp[SMALLSTRING+2]; + size_t content_sz; + uint8_t *buffer; + const char * ptr = tolstring(L, &content_sz, 2); + size_t cookie_sz = 0; + const char * cookie = luaL_checklstring(L,1,&cookie_sz); + size_t len = cookie_sz + content_sz; + + if (len > 0x10000) { + return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); + } + + if (len <= SMALLSTRING) { + buffer = tmp; + } else { + buffer = lua_newuserdata(L, len + 2); + } + + write_size(buffer, len); + memcpy(buffer+2, ptr, content_sz); + memcpy(buffer+2+content_sz, cookie, cookie_sz); + lua_pushlstring(L, (const char *)buffer, len+2); + + return 1; +} + static int ltostring(lua_State *L) { void * ptr = lua_touserdata(L, 1); @@ -458,8 +486,19 @@ ltostring(lua_State *L) { if (ptr == NULL) { lua_pushliteral(L, ""); } else { - lua_pushlstring(L, (const char *)ptr, size); - skynet_free(ptr); + if (lua_isnumber(L, 3)) { + int offset = lua_tointeger(L, 3); + if (offset < 0) { + return luaL_error(L, "Invalid offset %d", offset); + } + if (offset > size) { + offset = size; + } + lua_pushlstring(L, (const char *)ptr + offset, size-offset); + } else { + lua_pushlstring(L, (const char *)ptr, size); + skynet_free(ptr); + } } return 1; } @@ -471,6 +510,7 @@ luaopen_netpack(lua_State *L) { { "pop", lpop }, { "pack", lpack }, { "pack_string", lpack_string }, + { "pack_padding", lpack_padding }, { "clear", lclear }, { "tostring", ltostring }, { NULL, NULL }, diff --git a/lualib-src/lua-socket.c b/lualib-src/lua-socket.c index 8bfefc0e..916fa7a9 100644 --- a/lualib-src/lua-socket.c +++ b/lualib-src/lua-socket.c @@ -23,7 +23,6 @@ struct buffer_node { }; struct socket_buffer { - int limit; int size; int offset; struct buffer_node *head; @@ -66,7 +65,6 @@ lnewpool(lua_State *L, int sz) { static int lnewbuffer(lua_State *L) { struct socket_buffer * sb = lua_newuserdata(L, sizeof(*sb)); - sb->limit = luaL_optint(L,1,BUFFER_LIMIT); sb->size = 0; sb->offset = 0; sb->head = NULL; @@ -129,9 +127,6 @@ lpushbuffer(lua_State *L) { sb->size += sz; lua_pushinteger(L, sb->size); - if (sb->limit > 0 && sb->size > sb->limit) { - return luaL_error(L, "buffer overflow (limit = %d, size = %d)", sb->limit, sb->size); - } return 1; } diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua new file mode 100644 index 00000000..1fe507de --- /dev/null +++ b/lualib/http/httpd.lua @@ -0,0 +1,274 @@ +local table = table + +local httpd = {} + +local http_status_msg = { + [100] = "Continue", + [101] = "Switching Protocols", + [200] = "OK", + [201] = "Created", + [202] = "Accepted", + [203] = "Non-Authoritative Information", + [204] = "No Content", + [205] = "Reset Content", + [206] = "Partial Content", + [300] = "Multiple Choices", + [301] = "Moved Permanently", + [302] = "Found", + [303] = "See Other", + [304] = "Not Modified", + [305] = "Use Proxy", + [307] = "Temporary Redirect", + [400] = "Bad Request", + [401] = "Unauthorized", + [402] = "Payment Required", + [403] = "Forbidden", + [404] = "Not Found", + [405] = "Method Not Allowed", + [406] = "Not Acceptable", + [407] = "Proxy Authentication Required", + [408] = "Request Time-out", + [409] = "Conflict", + [410] = "Gone", + [411] = "Length Required", + [412] = "Precondition Failed", + [413] = "Request Entity Too Large", + [414] = "Request-URI Too Large", + [415] = "Unsupported Media Type", + [416] = "Requested range not satisfiable", + [417] = "Expectation Failed", + [500] = "Internal Server Error", + [501] = "Not Implemented", + [502] = "Bad Gateway", + [503] = "Service Unavailable", + [504] = "Gateway Time-out", + [505] = "HTTP Version not supported", +} + +local function recvheader(readbytes, limit, lines, header) + local result + local e = header:find("\r\n\r\n", 1, true) + if e then + result = header:sub(e+4) + else + while true do + local bytes = readbytes() + header = header .. bytes + if #header > limit then + return + end + e = header:find("\r\n\r\n", -#bytes-3, true) + if e then + result = header:sub(e+4) + break + end + end + end + local idx = 1 + for v in header:gmatch("(.-)\r\n") do + if v == "" then + break + end + lines[idx] = v + idx = idx + 1 + end + for i = idx, #lines do + lines[i] = nil + end + return result +end + +local function parseheader(lines, from, header) + local name, value + for i=from,#lines do + local line = lines[i] + if line:byte(1) == 9 then -- tab, append last line + if name == nil then + return + end + header[name] = header[name] .. line:sub(2) + else + name, value = line:match "^(.-):%s*(.*)" + if name == nil or value == nil then + return + end + name = name:lower() + if header[name] then + header[name] = header[name] .. ", " .. value + else + header[name] = value + end + end + end + return header +end + +local function chunksize(readbytes, body) + while true do + if #body > 128 then + return + end + body = body .. readbytes() + local f,e = body:find("\r\n",1,true) + if f then + return tonumber(body:sub(1,f-1),16), body:sub(e+1) + end + end +end + +local function readcrln(readbytes, body) + if #body > 2 then + if body:sub(1,2) ~= "\r\n" then + return + end + return body:sub(3) + else + body = body .. readbytes(2-#body) + if body ~= "\r\n" then + return + end + return "" + end +end + +local tmpline = {} + +local function recvchunkedbody(readbytes, bodylimit, header, body) + local result = "" + local size = 0 + + while true do + local sz + sz , body = chunksize(readbytes, body) + if not sz then + return + end + if sz == 0 then + break + end + size = size + sz + if bodylimit and size > bodylimit then + return + end + if #body >= sz then + result = result .. body:sub(1,sz) + body = body:sub(sz+1) + else + result = result .. body .. readbytes(sz - #body) + body = "" + end + body = readcrln(readbytes, body) + if not body then + return + end + end + + body = readcrln(readbytes, body) + if not body then + return + end + body = recvheader(readbytes, 8192, tmpline, body) + if not body then + return + end + + header = parseheader(tmpline,1,header) + + return result, header +end + +local function readall(readbytes, bodylimit) + local body = recvheader(readbytes, 8192, tmpline, "") + if not body then + return 413 -- Request Entity Too Large + end + local request = assert(tmpline[1]) + local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$" + assert(method and url and httpver) + httpver = assert(tonumber(httpver)) + if httpver < 1.0 or httpver > 1.1 then + return 505 -- HTTP Version not supported + end + local header = parseheader(tmpline,2,{}) + if not header then + return 400 -- Bad request + end + local length = header["content-length"] + if length then + length = tonumber(length) + end + local mode = header["transfer-encoding"] + if mode then + if mode ~= "identity" or mode ~= "chunked" then + return 501 -- Not Implemented + end + end + + if mode == "chunked" then + body, header = recvchunkedbody(readbytes, bodylimit, header, body) + if not body then + return 413 + end + else + -- identity mode + if length then + if length > bodylimit then + return 413 + end + if #body >= length then + body = body:sub(1,length) + else + local padding = readbytes(length - #body) + body = body .. padding + end + end + end + + return 200, url, method, header, body +end + +function httpd.read_request(...) + local ok, code, url, method, header, body = pcall(readall, ...) + if ok then + return code, url, method, header, body + else + return nil, code + end +end + +local function writeall(writefunc, statuscode, bodyfunc, header) + local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "") + writefunc(statusline) + if header then + for k,v in pairs(header) do + writefunc(string.format("%s: %s\r\n", k,v)) + end + end + local t = type(bodyfunc) + if t == "string" then + writefunc(string.format("content-length: %d\r\n\r\n", #bodyfunc)) + writefunc(bodyfunc) + elseif t == "function" then + writefunc("transfer-encoding: chunked\r\n") + while true do + local s = bodyfunc() + if s then + if s ~= "" then + writefunc(string.format("\r\n%x\r\n", #s)) + writefunc(s) + end + else + writefunc("\r\n0\r\n\r\n") + end + end + else + assert(t == "nil") + writefunc("\r\n") + end +end + +function httpd.write_response(...) + return pcall(writeall, ...) +end + +return httpd diff --git a/lualib/http/sockethelper.lua b/lualib/http/sockethelper.lua new file mode 100644 index 00000000..89c7ec93 --- /dev/null +++ b/lualib/http/sockethelper.lua @@ -0,0 +1,31 @@ +local socket = require "socket" + +local readbytes = socket.read +local writebytes = socket.write + +local sockethelper = {} +local socket_error = setmetatable({} , { __tostring = function() return "[Socket Error]" end }) + +sockethelper.socket_error = socket_error + +function sockethelper.readfunc(fd) + return function (sz) + local ret = readbytes(fd, sz) + if ret then + return ret + else + error(socket_error) + end + end +end + +function sockethelper.writefunc(fd) + return function(content) + local ok = writebytes(fd, content) + if not ok then + error(socket_error) + end + end +end + +return sockethelper diff --git a/lualib/http/url.lua b/lualib/http/url.lua new file mode 100644 index 00000000..ae74b099 --- /dev/null +++ b/lualib/http/url.lua @@ -0,0 +1,28 @@ +local url = {} + +local function decode_func(c) + return string.char(tonumber(c, 16)) +end + +local function decode(str) + local str = str:gsub('+', ' ') + return str:gsub("%%(..)", decode_func) +end + +function url.parse(u) + local path,query = u:match "([^?]*)%??(.*)" + if path then + path = decode(path) + end + return path, query +end + +function url.parse_query(q) + local r = {} + for k,v in q:gmatch "(.-)=([^&]*)&?" do + r[decode(k)] = decode(v) + end + return r +end + +return url diff --git a/lualib/mongo.lua b/lualib/mongo.lua index f9eece35..b1e87ca7 100644 --- a/lualib/mongo.lua +++ b/lualib/mongo.lua @@ -1,96 +1,125 @@ local bson = require "bson" local socket = require "socket" -local socketchannel = require "socketchannel" +local socketchannel = require "socketchannel" local skynet = require "skynet" local driver = require "mongo.driver" -local md5 = require "md5" +local md5 = require "md5" local rawget = rawget local assert = assert -local bson_encode = bson.encode -local bson_encode_order = bson.encode_order -local bson_decode = bson.decode +local bson_encode = bson.encode +local bson_encode_order = bson.encode_order +local bson_decode = bson.decode local empty_bson = bson_encode {} -local mongo = {} +local mongo = {} mongo.null = assert(bson.null) mongo.maxkey = assert(bson.maxkey) mongo.minkey = assert(bson.minkey) mongo.type = assert(bson.type) local mongo_cursor = {} -local cursor_meta = { - __index = mongo_cursor, +local cursor_meta = { + __index = mongo_cursor, } local mongo_client = {} -local client_meta = { - __index = function(self, key) - return rawget(mongo_client, key) or self:getDB(key) +local client_meta = { + __index = function(self, key) + return rawget(mongo_client, key) or self:getDB(key) end, __tostring = function (self) local port_string if self.port then - port_string = ":" .. tostring(self.port) + port_string = ":" .. tostring(self.port) else - port_string = "" + port_string = "" end - return "[mongo client : " .. self.host .. port_string .."]" + return "[mongo client : " .. self.host .. port_string .."]" end, - -- DO NOT need disconnect, because channel will shutdown during gc + -- DO NOT need disconnect, because channel will shutdown during gc } local mongo_db = {} -local db_meta = { - __index = function (self, key) - return rawget(mongo_db, key) or self:getCollection(key) +local db_meta = { + __index = function (self, key) + return rawget(mongo_db, key) or self:getCollection(key) end, __tostring = function (self) - return "[mongo db : " .. self.name .. "]" + return "[mongo db : " .. self.name .. "]" end } local mongo_collection = {} -local collection_meta = { - __index = function(self, key) - return rawget(mongo_collection, key) or self:getCollection(key) - end , +local collection_meta = { + __index = function(self, key) + return rawget(mongo_collection, key) or self:getCollection(key) + end , __tostring = function (self) - return "[mongo collection : " .. self.full_name .. "]" + return "[mongo collection : " .. self.full_name .. "]" end } local function dispatch_reply(so) - local len_reply = so:read(4) - local reply = so:read(driver.length(len_reply)) - local result = { result = {} } - local succ, reply_id, document, cursor_id, startfrom = driver.reply(reply, result.result) - result.document = document + local len_reply = so:read(4) + local reply = so:read(driver.length(len_reply)) + local result = { result = {} } + local succ, reply_id, document, cursor_id, startfrom = driver.reply(reply, result.result) + result.document = document result.cursor_id = cursor_id result.startfrom = startfrom - result.data = reply + result.data = reply return reply_id, succ, result end -local function mongo_auth(mongoc) - local user = rawget(mongoc, "username") - local pass = rawget(mongoc, "password") +local function __parse_addr(addr) + local host, port = string.match(addr, "([^:]+):(.+)") + return host, tonumber(port) +end + +local function mongo_auth(mongoc) + local user = rawget(mongoc, "username") + local pass = rawget(mongoc, "password") - if user == nil or pass == nil then - return - end return function() - assert(mongoc:auth(user, pass)) + if user ~= nil and pass ~= nil then + assert(mongoc:auth(user, pass)) + end + local rs_data = mongoc:runCommand("ismaster") + if rs_data.ok == 1 then + if rs_data.hosts then + local backup = {} + for _, v in ipairs(rs_data.hosts) do + local host, port = __parse_addr(v) + table.insert(backup, {host = host, port = port}) + end + mongoc.__sock:changebackup(backup) + end + if rs_data.ismaster then + return + else + local host, port = __parse_addr(rs_data.primary) + mongoc.host = host + mongoc.port = port + mongoc.__sock:changehost(host, port) + end + end end end -function mongo.client( conf ) - local obj = { - host = conf.host, - port = conf.port or 27017, +function mongo.client( conf ) + local first = conf + local backup = nil + if conf.rs then + first = conf.rs[1] + backup = conf.rs + end + local obj = { + host = first.host, + port = first.port or 27017, } obj.__id = 0 @@ -99,9 +128,10 @@ function mongo.client( conf ) port = obj.port, response = dispatch_reply, auth = mongo_auth(obj), + backup = backup, } setmetatable(obj, client_meta) - obj.__sock:connect(true) -- try connect only once + obj.__sock:connect(true) -- try connect only once return obj end @@ -109,32 +139,32 @@ function mongo_client:getDB(dbname) local db = { connection = self, name = dbname, - full_name = dbname, + full_name = dbname, database = false, - __cmd = dbname .. "." .. "$cmd", + __cmd = dbname .. "." .. "$cmd", } - db.database = db + db.database = db - return setmetatable(db, db_meta) + return setmetatable(db, db_meta) end function mongo_client:disconnect() if self.__sock then local so = self.__sock - self.__sock = false + self.__sock = false so:close() end end function mongo_client:genId() local id = self.__id + 1 - self.__id = id + self.__id = id return id end function mongo_client:runCommand(...) if not self.admin then - self.admin = self:getDB "admin" + self.admin = self:getDB "admin" end return self.admin:runCommand(...) end @@ -146,14 +176,14 @@ function mongo_client:auth(user,password) return false end - local key = md5.sumhexa(string.format("%s%s%s",result.nonce,user,password)) + local key = md5.sumhexa(string.format("%s%s%s",result.nonce,user,password)) local result= self:runCommand ("authenticate",1,"user",user,"nonce",result.nonce,"key",key) - return result.ok == 1 + return result.ok == 1 end function mongo_client:logout() local result = self:runCommand "logout" - return result.ok == 1 + return result.ok == 1 end function mongo_db:runCommand(cmd,cmd_v,...) @@ -166,18 +196,18 @@ function mongo_db:runCommand(cmd,cmd_v,...) else bson_cmd = bson_encode_order(cmd,cmd_v,...) end - local pack = driver.query(request_id, 0, self.__cmd, 0, 1, bson_cmd) - -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) - local req = sock:request(pack, request_id) - local doc = req.document + local pack = driver.query(request_id, 0, self.__cmd, 0, 1, bson_cmd) + -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) + local req = sock:request(pack, request_id) + local doc = req.document return bson_decode(doc) end function mongo_db:getCollection(collection) - local col = { + local col = { connection = self.connection, name = collection, - full_name = self.full_name .. "." .. collection, + full_name = self.full_name .. "." .. collection, database = self.database, } self[collection] = setmetatable(col, collection_meta) @@ -188,20 +218,20 @@ mongo_collection.getCollection = mongo_db.getCollection function mongo_collection:insert(doc) if doc._id == nil then - doc._id = bson.objectid() + doc._id = bson.objectid() end local sock = self.connection.__sock local pack = driver.insert(0, self.full_name, bson_encode(doc)) - -- flags support 1: ContinueOnError + -- flags support 1: ContinueOnError sock:request(pack) end function mongo_collection:batch_insert(docs) - for i=1,#docs do + for i=1,#docs do if docs[i]._id == nil then - docs[i]._id = bson.objectid() + docs[i]._id = bson.objectid() end - docs[i] = bson_encode(docs[i]) + docs[i] = bson_encode(docs[i]) end local sock = self.connection.__sock local pack = driver.insert(0, self.full_name, docs) @@ -209,7 +239,7 @@ function mongo_collection:batch_insert(docs) end function mongo_collection:update(selector,update,upsert,multi) - local flags = (upsert and 1 or 0) + (multi and 2 or 0) + local flags = (upsert and 1 or 0) + (multi and 2 or 0) local sock = self.connection.__sock local pack = driver.update(self.full_name, flags, bson_encode(selector), bson_encode(update)) sock:request(pack) @@ -225,24 +255,24 @@ function mongo_collection:findOne(query, selector) local conn = self.connection local request_id = conn:genId() local sock = conn.__sock - local pack = driver.query(request_id, 0, self.full_name, 0, 1, query and bson_encode(query) or empty_bson, selector and bson_encode(selector)) - -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) - local req = sock:request(pack, request_id) - local doc = req.document + local pack = driver.query(request_id, 0, self.full_name, 0, 1, query and bson_encode(query) or empty_bson, selector and bson_encode(selector)) + -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) + local req = sock:request(pack, request_id) + local doc = req.document return bson_decode(doc) end function mongo_collection:find(query, selector) return setmetatable( { __collection = self, - __query = query and bson_encode(query) or empty_bson, + __query = query and bson_encode(query) or empty_bson, __selector = selector and bson_encode(selector), - __ptr = nil, + __ptr = nil, __data = nil, __cursor = nil, __document = {}, - __flags = 0, - } , cursor_meta) + __flags = 0, + } , cursor_meta) end function mongo_cursor:hasNext() @@ -255,42 +285,42 @@ function mongo_cursor:hasNext() local sock = conn.__sock local pack if self.__data == nil then - pack = driver.query(request_id, self.__flags, self.__collection.full_name,0,0,self.__query,self.__selector) + pack = driver.query(request_id, self.__flags, self.__collection.full_name,0,0,self.__query,self.__selector) else if self.__cursor then pack = driver.more(request_id, self.__collection.full_name,0,self.__cursor) else -- no more - self.__document = nil - self.__data = nil + self.__document = nil + self.__data = nil return false end end local ok, result = pcall(sock.request,sock,pack, request_id) - local doc = result.document + local doc = result.document local cursor = result.cursor_id if ok then if doc then - self.__document = result.result - self.__data = result.data + self.__document = result.result + self.__data = result.data self.__ptr = 1 - self.__cursor = cursor + self.__cursor = cursor return true else - self.__document = nil - self.__data = nil - self.__cursor = nil + self.__document = nil + self.__data = nil + self.__cursor = nil return false end else - self.__document = nil - self.__data = nil - self.__cursor = nil + self.__document = nil + self.__data = nil + self.__cursor = nil if doc then - local err = bson_decode(doc) + local err = bson_decode(doc) error(err["$err"]) else error("Reply from mongod error") @@ -303,11 +333,11 @@ end function mongo_cursor:next() if self.__ptr == nil then - error "Call hasNext first" + error "Call hasNext first" end - local r = bson_decode(self.__document[self.__ptr]) - self.__ptr = self.__ptr + 1 - if self.__ptr > #self.__document then + local r = bson_decode(self.__document[self.__ptr]) + self.__ptr = self.__ptr + 1 + if self.__ptr > #self.__document then self.__ptr = nil end diff --git a/lualib/skynet.lua b/lualib/skynet.lua index 2b984e2a..5e8e95b0 100644 --- a/lualib/skynet.lua +++ b/lualib/skynet.lua @@ -127,7 +127,7 @@ function suspend(co, result, command, param, size) if not result then local session = session_coroutine_id[co] local addr = session_coroutine_address[co] - if session and session ~= 0 then + if session then c.send(addr, skynet.PTYPE_ERROR, session, "") end session_coroutine_id[co] = nil @@ -151,6 +151,9 @@ function suspend(co, result, command, param, size) -- coroutine exit session_coroutine_id[co] = nil session_coroutine_address[co] = nil + elseif command == "QUIT" then + -- service exit + return else error("Unknown command : " .. command .. "\n" .. debug.traceback(co)) end @@ -264,6 +267,8 @@ function skynet.exit() end end c.command("EXIT") + -- quit service + coroutine_yield "QUIT" end function skynet.kill(name) diff --git a/lualib/snax/gateserver.lua b/lualib/snax/gateserver.lua new file mode 100644 index 00000000..fc019143 --- /dev/null +++ b/lualib/snax/gateserver.lua @@ -0,0 +1,130 @@ +local skynet = require "skynet" +local netpack = require "netpack" +local socketdriver = require "socketdriver" + +local gateserver = {} + +local socket -- listen socket +local queue -- message queue +local maxclient -- max client +local client_number = 0 +local CMD = setmetatable({}, { __gc = function() netpack.clear(queue) end }) +local nodelay = false + +local connection = {} + +function gateserver.openclient(fd) + if connection[fd] then + socketdriver.start(fd) + end +end + +function gateserver.closeclient(fd) + local c = connection[fd] + if c then + connection[fd] = false + socketdriver.close(fd) + end +end + +function gateserver.start(handler) + assert(handler.message) + assert(handler.connect) + + function CMD.open( source, conf ) + assert(not socket) + local address = conf.address or "0.0.0.0" + local port = assert(conf.port) + maxclient = conf.maxclient or 1024 + nodelay = conf.nodelay + socket = socketdriver.listen(address, port) + socketdriver.start(socket) + if handler.open then + return handler.open(source, conf) + end + end + + function CMD.close() + assert(socket) + socketdriver.close(socket) + socket = nil + end + + local MSG = {} + + function MSG.data(fd, msg, sz) + if connection[fd] then + handler.message(fd, msg, sz) + end + end + + function MSG.more() + for fd, msg, sz in netpack.pop, queue do + if connection[fd] then + handler.message(fd, msg, sz) + end + end + end + + function MSG.open(fd, msg) + if client_number >= maxclient then + socketdriver.close(fd) + return + end + if nodelay then + socketdriver.nodelay(fd) + end + connection[fd] = true + client_number = client_number + 1 + handler.connect(fd, msg) + end + + local function close_fd(fd) + local c = connection[fd] + if c ~= nil then + connection[fd] = nil + client_number = client_number - 1 + end + end + + function MSG.close(fd) + if handler.disconnect then + handler.disconnect(fd) + end + close_fd(fd) + end + + function MSG.error(fd, msg) + if handler.error then + handler.error(fd) + end + close_fd(fd) + end + + skynet.register_protocol { + name = "socket", + id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6 + unpack = function ( msg, sz ) + return netpack.filter( queue, msg, sz) + end, + dispatch = function (_, _, q, type, ...) + queue = q + if type then + MSG[type](...) + end + end + } + + skynet.start(function() + skynet.dispatch("lua", function (_, address, cmd, ...) + local f = CMD[cmd] + if f then + skynet.ret(skynet.pack(f(address, ...))) + else + skynet.ret(skynet.pack(handler.command(cmd, address, ...))) + end + end) + end) +end + +return gateserver \ No newline at end of file diff --git a/lualib/snax/loginserver.lua b/lualib/snax/loginserver.lua new file mode 100644 index 00000000..c5de769f --- /dev/null +++ b/lualib/snax/loginserver.lua @@ -0,0 +1,197 @@ +local skynet = require "skynet" +local socket = require "socket" +local crypt = require "crypt" +local table = table +local string = string +local assert = assert + +--[[ + +Protocol: + + line (\n) based text protocol + + 1. Server->Client : base64(8bytes random challenge) + 2. Client->Server : base64(8bytes handshake client key) + 3. Server: Gen a 8bytes handshake server key + 4. Server->Client : base64(DH-Exchange(server key)) + 5. Server/Client secret := DH-Secret(client key/server key) + 6. Client->Server : base64(HMAC(challenge, secret)) + 7. Client->Server : DES(secret, base64(token)) + 8. Server : call auth_handler(token) -> server, uid (A user defined method) + 9. Server : call login_handler(server, uid, secret) ->subid (A user defined method) + 10. Server->Client : 200 base64(subid) + +Error Code: + 400 Bad Request . challenge failed + 401 Unauthorized . unauthorized by auth_handler + 403 Forbidden . login_handler failed + 406 Not Acceptable . already in login (disallow multi login) + +Success: + 200 base64(subid) +]] + +local socket_error = {} +local function assert_socket(v, fd) + if v then + return v + else + skynet.error(string.format("auth failed: socket (fd = %d) closed", fd)) + error(socket_error) + end +end + +local function write(fd, text) + assert_socket(socket.write(fd, text), fd) +end + +local function launch_slave(auth_handler) + local function auth(fd, addr) + fd = assert(tonumber(fd)) + skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) + socket.start(fd) + + -- set socket buffer limit (8K) + -- If the attacker send large package, close the socket + socket.limit(fd, 8192) + + local challenge = crypt.randomkey() + write(fd, crypt.base64encode(challenge).."\n") + + local handshake = assert_socket(socket.readline(fd), fd) + local clientkey = crypt.base64decode(handshake) + if #clientkey ~= 8 then + error "Invalid client key" + end + local serverkey = crypt.randomkey() + write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") + + local secret = crypt.dhsecret(clientkey, serverkey) + + local response = assert_socket(socket.readline(fd), fd) + local hmac = crypt.hmac64(challenge, secret) + + if hmac ~= crypt.base64decode(response) then + write(fd, "400 Bad Request\n") + error "challenge failed" + end + + local etoken = assert_socket(socket.readline(fd),fd) + + local token = crypt.desdecode(secret, crypt.base64decode(etoken)) + + local ok, server, uid = pcall(auth_handler,token) + + socket.abandon(fd) + return ok, server, uid, secret + end + + local function ret_pack(ok, err, ...) + if ok then + skynet.ret(skynet.pack(err, ...)) + elseif err ~= socket_error then + error(err) + end + end + + skynet.dispatch("lua", function(_,_,...) + ret_pack(pcall(auth, ...)) + end) +end + +local user_login = {} + +local function accept(conf, s, fd, addr) + -- call slave auth + local ok, server, uid, secret = skynet.call(s, "lua", fd, addr) + socket.start(fd) + + if not ok then + write(fd, "401 Unauthorized\n") + error(server) + end + + if not conf.multilogin then + if user_login[uid] then + write(fd, "406 Not Acceptable\n") + error(string.format("User %s is already login", uid)) + end + + user_login[uid] = true + end + + local ok, err = pcall(conf.login_handler, server, uid, secret) + -- unlock login + user_login[uid] = nil + + if ok then + err = err or "" + write(fd, "200 "..crypt.base64encode(err).."\n") + else + write(fd, "403 Forbidden\n") + error(err) + end +end + +local function launch_master(conf) + local instance = conf.instance or 8 + assert(instance > 0) + local host = conf.host or "0.0.0.0" + local port = assert(tonumber(conf.port)) + local slave = {} + local balance = 1 + + skynet.dispatch("lua", function(_,source,command, ...) + if command == "register_slave" then + table.insert(slave, source) + skynet.ret(skynet.pack(nil)) + else + skynet.ret(skynet.pack(conf.command_handler(command, ...))) + end + end) + + for i=1,instance do + skynet.newservice(SERVICE_NAME) + end + + skynet.error(string.format("login server listen at : %s %d", host, port)) + local id = socket.listen(host, port) + socket.start(id , function(fd, addr) + local s = slave[balance] + balance = balance + 1 + if balance > #slave then + balance = 1 + end + local ok, err = pcall(accept, conf, s, fd, addr) + if not ok then + if err ~= socket_error then + skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err)) + end + end + socket.close(fd) + end) +end + +local function login(conf) + local name = "." .. (conf.name or "login") + skynet.start(function() + local loginmaster = skynet.localname(name) + if loginmaster then + skynet.call(loginmaster, "lua", "register_slave") + local auth_handler = assert(conf.auth_handler) + launch_master = nil + conf = nil + launch_slave(auth_handler) + else + launch_slave = nil + conf.auth_handler = nil + assert(conf.login_handler) + assert(conf.command_handler) + skynet.register(name) + launch_master(conf) + end + end) +end + +return login diff --git a/lualib/snax/msgserver.lua b/lualib/snax/msgserver.lua new file mode 100644 index 00000000..f6804a60 --- /dev/null +++ b/lualib/snax/msgserver.lua @@ -0,0 +1,318 @@ +local skynet = require "skynet" +local gateserver = require "snax.gateserver" +local netpack = require "netpack" +local crypt = require "crypt" +local socketdriver = require "socketdriver" +local assert = assert +local b64encode = crypt.base64encode +local b64decode = crypt.base64decode + +--[[ + +Protocol: + + All the number type is big-endian + + Shakehands (The first package) + + Client -> Server : + + base64(uid)@base64(server)#base64(subid):index:base64(hmac) + + Server -> Client + + XXX ErrorCode + 404 User Not Found + 403 Index Expired + 401 Unauthorized + 400 Bad Request + 200 OK + + Req-Resp + + Client -> Server : Request + word size (Not include self) + string content (size-4) + dword session + + Server -> Client : Response + word size (Not include self) + string content (size-5) + byte ok (1 is ok, 0 is error) + dword session + +API: + server.userid(username) + return uid, subid, server + + server.username(uid, subid, server) + return username + + server.login(username, secret) + update user secret + + server.logout(username) + user logout + + server.ip(username) + return ip when connection establish, or nil + + server.start(conf) + start server + +Supported skynet command: + kick username (may used by loginserver) + login username secret (used by loginserver) + logout username (used by agent) + +Config for server.start: + conf.expired_number : the number of the response message cached after sending out (default is 128) + conf.login_handler(uid, secret) -> subid : the function when a new user login, alloc a subid for it. (may call by login server) + conf.logout_handler(uid, subid) : the functon when a user logout. (may call by agent) + conf.kick_handler(uid, subid) : the functon when a user logout. (may call by login server) + conf.request_handler(username, session, msg, sz) : the function when recv a new request. + conf.register_handler(servername) : call when gate open + conf.disconnect_handler(username) : call when a connection disconnect (afk) +]] + +local server = {} + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local user_online = {} +local handshake = {} +local connection = {} + +function server.userid(username) + -- base64(uid)@base64(server)#base64(subid) + local uid, servername, subid = username:match "([^@]*)@([^#]*)#(.*)" + return b64decode(uid), b64decode(subid), b64decode(servername) +end + +function server.username(uid, subid, servername) + return string.format("%s@%s#%s", b64encode(uid), b64encode(servername), b64encode(tostring(subid))) +end + +function server.logout(username) + local u = user_online[username] + user_online[username] = nil + if u.fd then + gateserver.closeclient(u.fd) + connection[u.fd] = nil + end +end + +function server.login(username, secret) + assert(user_online[username] == nil) + user_online[username] = { + secret = secret, + version = 0, + index = 0, + username = username, + response = {}, -- response cache + } +end + +function server.ip(username) + local u = user_online[username] + if u and u.fd then + return u.ip + end +end + +function server.start(conf) + local expired_number = conf.expired_number or 128 + + local handler = {} + + local CMD = { + login = assert(conf.login_handler), + logout = assert(conf.logout_handler), + kick = assert(conf.kick_handler), + } + + function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(...) + end + + function handler.open(source, gateconf) + local servername = assert(gateconf.servername) + return conf.register_handler(servername) + end + + function handler.connect(fd, addr) + handshake[fd] = addr + gateserver.openclient(fd) + end + + function handler.disconnect(fd) + handshake[fd] = nil + local c = connection[fd] + if c then + c.fd = nil + connection[fd] = nil + if conf.disconnect_handler then + conf.disconnect_handler(c.username) + end + end + end + + handler.error = handler.disconnect + + -- atomic , no yield + local function do_auth(fd, message, addr) + local username, index, hmac = string.match(message, "([^:]*):([^:]*):([^:]*)") + local u = user_online[username] + if u == nil then + return "404 User Not Found" + end + local idx = assert(tonumber(index)) + hmac = b64decode(hmac) + + if idx <= u.version then + return "403 Index Expired" + end + + local text = string.format("%s:%s", username, index) + local v = crypt.hmac64(crypt.hashkey(text), u.secret) + if v ~= hmac then + return "401 Unauthorized" + end + + u.version = idx + u.fd = fd + u.ip = addr + connection[fd] = u + end + + local function auth(fd, addr, msg, sz) + local message = netpack.tostring(msg, sz) + local ok, result = pcall(do_auth, fd, message, addr) + if not ok then + skynet.error(result) + result = "400 Bad Request" + end + + local close = result ~= nil + + if result == nil then + result = "200 OK" + end + + socketdriver.send(fd, netpack.pack(result)) + + if close then + gateserver.closeclient(fd) + end + end + + local request_handler = assert(conf.request_handler) + + -- u.response is a struct { return_fd , response, version, index } + local function retire_response(u) + if u.index >= expired_number * 2 then + local max = 0 + local response = u.response + for k,p in pairs(response) do + if p[1] == nil then + -- request complete, check expired + if p[4] < expired_number then + response[k] = nil + else + p[4] = p[4] - expired_number + if p[4] > max then + max = p[4] + end + end + end + end + u.index = max + 1 + end + end + + local function do_request(fd, msg, sz) + local u = assert(connection[fd], "invalid fd") + local msg_sz = sz - 4 + local session = netpack.tostring(msg, sz, msg_sz) + local p = u.response[session] + if p then + -- session can be reuse in the same connection + if p[3] == u.version then + local last = u.response[session] + u.response[session] = nil + p = nil + if last[2] == nil then + local error_msg = string.format("Conflict session %s", crypt.hexencode(session)) + skynet.error(error_msg) + error(error_msg) + end + end + end + + if p == nil then + p = { fd } + u.response[session] = p + local ok, result = pcall(conf.request_handler, u.username, msg, msg_sz) + result = result or "" + -- NOTICE: YIELD here, socket may close. + if not ok then + skynet.error(result) + result = "\0" .. session + else + result = result .. '\1' .. session + end + + p[2] = netpack.pack_string(result) + p[3] = u.version + p[4] = u.index + else + netpack.tostring(msg, sz) -- request before, so free msg + -- update version/index, change return fd. + -- resend response. + p[1] = fd + p[3] = u.version + p[4] = u.index + if p[2] == nil then + -- already request, but response is not ready + return + end + end + u.index = u.index + 1 + -- the return fd is p[1] (fd may change by multi request) check connect + fd = p[1] + if connection[fd] then + socketdriver.send(fd, p[2]) + end + p[1] = nil + retire_response(u) + end + + local function request(fd, msg, sz) + local ok, err = pcall(do_request, fd, msg, sz) + -- not atomic, may yield + if not ok then + skynet.error(string.format("Invalid package %s : %s", err, netpack.tostring(msg, sz))) + if connection[fd] then + gateserver.closeclient(fd) + end + end + end + + function handler.message(fd, msg, sz) + local addr = handshake[fd] + if addr then + auth(fd,addr,msg,sz) + handshake[fd] = nil + else + request(fd, msg, sz) + end + end + + return gateserver.start(handler) +end + +return server diff --git a/lualib/socket.lua b/lualib/socket.lua index 28268747..f54d2175 100644 --- a/lualib/socket.lua +++ b/lualib/socket.lua @@ -2,7 +2,6 @@ local driver = require "socketdriver" local skynet = require "skynet" local assert = assert -local buffer_limit = -1 local socket = {} -- api local buffer_pool = {} -- store all message buffer object local socket_pool = setmetatable( -- store all socket object @@ -48,13 +47,7 @@ socket_message[1] = function(id, size, data) return end - local ok , sz = pcall(driver.push, s.buffer, buffer_pool, data, size) - if not ok then - skynet.error("socket: error on ", id , sz) - driver.clear(s.buffer,buffer_pool) - driver.close(id) - return - end + local sz = driver.push(s.buffer, buffer_pool, data, size) local rr = s.read_required local rrt = type(rr) if rrt == "number" then @@ -63,11 +56,19 @@ socket_message[1] = function(id, size, data) s.read_required = nil wakeup(s) end - elseif rrt == "string" then - -- read line - if driver.readline(s.buffer,nil,rr) then - s.read_required = nil - wakeup(s) + else + if s.buffer_limit and sz > s.buffer_limit then + skynet.error(string.format("socket buffer overlow: fd=%d size=%d", id , sz)) + driver.clear(s.buffer,buffer_pool) + driver.close(id) + return + end + if rrt == "string" then + -- read line + if driver.readline(s.buffer,nil,rr) then + s.read_required = nil + wakeup(s) + end end end end @@ -130,7 +131,7 @@ skynet.register_protocol { local function connect(id, func) local newbuffer if func == nil then - newbuffer = driver.buffer(buffer_limit) + newbuffer = driver.buffer() end local s = { id = id, @@ -206,6 +207,27 @@ end function socket.read(id, sz) local s = socket_pool[id] assert(s) + if sz == nil then + -- read some bytes + local ret = driver.readall(s.buffer, buffer_pool) + if ret ~= "" then + return ret + end + + if not s.connected then + return false, ret + end + assert(not s.read_required) + s.read_required = 0 + suspend(s) + ret = driver.readall(s.buffer, buffer_pool) + if ret ~= "" then + return ret + else + return false, ret + end + end + local ret = driver.pop(s.buffer, buffer_pool, sz) if ret then return ret @@ -325,8 +347,9 @@ function socket.abandon(id) socket_pool[id] = nil end -function socket.limit(limit) - buffer_limit = limit +function socket.limit(id, limit) + local s = assert(socket_pool[id]) + s.buffer_limit = limit end return socket diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index e6a061f0..45aeea54 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -26,6 +26,7 @@ function socket_channel.channel(desc) local c = { __host = assert(desc.host), __port = assert(desc.port), + __backup = desc.backup, __auth = desc.auth, __response = desc.response, -- It's for session mode __request = {}, -- request seq { response func or session } -- It's for order mode @@ -152,34 +153,66 @@ local function dispatch_function(self) end end +local function connect_backup(self) + if self.__backup then + for _, addr in ipairs(self.__backup) do + local host, port + if type(addr) == "table" then + host, port = addr.host, addr.port + else + host = addr + port = self.__port + end + skynet.error("socket: connect to backup host", host, port) + local fd = socket.open(host, port) + if fd then + self.__host = host + self.__port = port + return fd + end + end + end +end + local function connect_once(self) + if self.__closed then + return false + end assert(not self.__sock and not self.__authcoroutine) local fd = socket.open(self.__host, self.__port) if not fd then - return false + fd = connect_backup(self) + if not fd then + return false + end end - self.__authcoroutine = coroutine.running() + self.__sock = setmetatable( {fd} , channel_socket_meta ) skynet.fork(dispatch_function(self), self) if self.__auth then + self.__authcoroutine = coroutine.running() local ok , message = pcall(self.__auth, self) if not ok then close_channel_socket(self) if message ~= socket_error then + self.__authcoroutine = false skynet.error("socket: auth failed", message) end end self.__authcoroutine = false + if ok and not self.__sock then + -- auth may change host, so connect again + return connect_once(self) + end return ok end - self.__authcoroutine = false return true end local function try_connect(self , once) - local t = 100 + local t = 0 while not self.__closed do if connect_once(self) then if not once then @@ -292,6 +325,20 @@ function channel:close() end end +function channel:changehost(host, port) + self.__host = host + if port then + self.__port = port + end + if not self.__closed then + close_channel_socket(self) + end +end + +function channel:changebackup(backup) + self.__backup = backup +end + channel_meta.__gc = channel.close local function wrapper_socket_function(f) diff --git a/service/gate.lua b/service/gate.lua index 772f135a..5fb22801 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -1,92 +1,23 @@ local skynet = require "skynet" +local gateserver = require "snax.gateserver" local netpack = require "netpack" -local socketdriver = require "socketdriver" -local socket -local queue local watchdog -local maxclient -local client_number = 0 -local CMD = setmetatable({}, { __gc = function() netpack.clear(queue) end }) -local nodelay = false - local connection = {} -- fd -> connection : { fd , client, agent , ip, mode } local forwarding = {} -- agent -> connection -function CMD.open( source , conf ) - assert(not socket) - local address = conf.address or "0.0.0.0" - local port = assert(conf.port) - maxclient = conf.maxclient or 1024 +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local handler = {} + +function handler.open(source, conf) watchdog = conf.watchdog or source - socket = socketdriver.listen(address, port) - socketdriver.start(socket) end -function CMD.nodelay(source, v) - if v ~= false then - v = true - end - nodelay = v -end - -function CMD.close() - assert(socket) - socketdriver.close(socket) - socket = nil -end - -local function unforward(c) - if c.agent then - forwarding[c.agent] = nil - c.agent = nil - c.client = nil - end -end - -local function start(c) - if not c.mode then - c.mode = "open" - socketdriver.start(c.fd) - end -end - -function CMD.forward(source, fd, client, address) - local c = assert(connection[fd]) - unforward(c) - start(c) - - c.client = client or 0 - c.agent = address or source - - forwarding[c.agent] = c -end - -function CMD.accept(source, fd) - local c = assert(connection[fd]) - unforward(c) - start(c) -end - -function CMD.kick(source, fd) - local c - if fd then - c = connection[fd] - else - c = forwarding[source] - end - - assert(c) - - if c.mode ~= "close" then - c.mode = "close" - socketdriver.close(c.fd) - end -end - -local MSG = {} - -function MSG.data(fd, msg, sz) +function handler.message(fd, msg, sz) -- recv a package, forward it local c = connection[fd] local agent = c.agent @@ -97,70 +28,65 @@ function MSG.data(fd, msg, sz) end end -function MSG.more() - for fd, msg, sz in netpack.pop, queue do - MSG.data(fd, msg, sz) - end -end - -function MSG.open(fd, msg) - if client_number >= maxclient then - socketdriver.close(fd) - return - end +function handler.connect(fd, addr) local c = { fd = fd, ip = msg, } connection[fd] = c - client_number = client_number + 1 - if nodelay then - socketdriver.nodelay(fd) - end - skynet.send(watchdog, "lua", "socket", "open", fd, msg) + skynet.send(watchdog, "lua", "socket", "open", fd, addr) end -local function close_fd(fd, message) +local function unforward(c) + if c.agent then + forwarding[c.agent] = nil + c.agent = nil + c.client = nil + end +end + +local function close_fd(fd) local c = connection[fd] if c then unforward(c) connection[fd] = nil - client_number = client_number - 1 end end -function MSG.close(fd) +function handler.disconnect(fd) close_fd(fd) skynet.send(watchdog, "lua", "socket", "close", fd) end -function MSG.error(fd, msg) +function handler.error(fd, msg) close_fd(fd) skynet.send(watchdog, "lua", "socket", "error", fd, msg) end -skynet.register_protocol { - name = "socket", - id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6 - unpack = function ( msg, sz ) - return netpack.filter( queue, msg, sz) - end, - dispatch = function (_, _, q, type, ...) - queue = q - if type then - MSG[type](...) - end - end -} +local CMD = {} -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, -} +function CMD.forward(source, fd, client, address) + local c = assert(connection[fd]) + unforward(c) + c.client = client or 0 + c.agent = address or source + forwarding[c.agent] = c + gateserver.openclient(fd) +end -skynet.start(function() - skynet.dispatch("lua", function (_, address, cmd, ...) - local f = assert(CMD[cmd]) - skynet.ret(skynet.pack(f(address, ...))) - end) -end) +function CMD.accept(source, fd) + local c = assert(connection[fd]) + unforward(c) + gateserver.openclient(fd) +end + +function CMD.kick(source, fd) + gateserver.closeclient(fd) +end + +function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(source, ...) +end + +gateserver.start(handler) diff --git a/skynet-src/skynet_main.c b/skynet-src/skynet_main.c index 9c38fd25..4eb18a59 100644 --- a/skynet-src/skynet_main.c +++ b/skynet-src/skynet_main.c @@ -11,6 +11,7 @@ #include #include #include +#include static int optint(const char *key, int opt) { @@ -51,7 +52,6 @@ optstring(const char *key,const char * opt) { static void _init_env(lua_State *L) { - lua_pushglobaltable(L); lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { int keyt = lua_type(L, -2); @@ -83,6 +83,19 @@ int sigign() { return 0; } +static const char * load_config = "\ + local config_name = ...\ + local f = assert(io.open(config_name))\ + local code = assert(f:read \'*a\')\ + local function getenv(name) return assert(os.getenv(name), name) end\ + code = string.gsub(code, \'%$([%w_%d]+)\', getenv)\ + print(code)\ + f:close()\ + local result = {}\ + assert(load(code,\'=(load)\',\'t\',result))()\ + return result\ +"; + int main(int argc, char *argv[]) { const char * config_file = "config"; @@ -98,11 +111,12 @@ main(int argc, char *argv[]) { struct lua_State *L = lua_newstate(skynet_lalloc, NULL); luaL_openlibs(L); // link lua lib - lua_close(L); - L = luaL_newstate(); - - int err = luaL_dofile(L, config_file); + int err = luaL_loadstring(L, load_config); + assert(err == LUA_OK); + lua_pushstring(L, config_file); + + err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); diff --git a/test/testsocket.lua b/test/testsocket.lua index 05313a11..3893cd61 100644 --- a/test/testsocket.lua +++ b/test/testsocket.lua @@ -7,9 +7,9 @@ local function echo(id) socket.start(id) while true do - local str = socket.readline(id,"\n") + local str = socket.read(id) if str then - socket.write(id, str .. "\n") + socket.write(id, str) else socket.close(id) return @@ -21,9 +21,6 @@ if mode == "agent" then id = tonumber(id) skynet.start(function() - -- A small limit, if socket buffer overflow, close the client - socket.limit(64) - skynet.fork(function() echo(id) skynet.exit()