Commits

leaf corcoran committed e740fc0

okay

Comments (0)

Files changed (38)

+
+install:
+	echo "hello!"
+

heroku-openresty-dev-1.rockspec

+
+package = "heroku-openresty"
+version = "dev-1"
+
+source = {
+	url = "file:///srv/git/heroku-openresty.git/"
+}
+
+description = {
+	summary = "openresty binaries for running on heroku",
+	license = "MIT"
+}
+
+dependencies = {
+	"lua >= 5.1"
+}
+
+build = {
+	type = "makefile"
+}

lualib/cjson.so

Binary file added.

lualib/rds/parser.so

Binary file added.

lualib/redis/parser.so

Binary file added.

lualib/resty/aes.lua

+module("resty.aes", package.seeall)
+
+_VERSION = '0.06'
+
+--local asn1 = require "resty.asn1"
+local ffi = require "ffi"
+local ffi_new = ffi.new
+local ffi_gc = ffi.gc
+local ffi_str = ffi.string
+local ffi_copy = ffi.copy
+local C = ffi.C
+
+local mt = { __index = resty.aes }
+
+ffi.cdef[[
+typedef struct engine_st ENGINE;
+
+typedef struct evp_cipher_st EVP_CIPHER;
+typedef struct evp_cipher_ctx_st
+{
+const EVP_CIPHER *cipher;
+ENGINE *engine;
+int encrypt;
+int buf_len;
+
+unsigned char  oiv[16];
+unsigned char  iv[16];
+unsigned char buf[32];
+int num;
+
+void *app_data;
+int key_len;
+unsigned long flags;
+void *cipher_data;
+int final_used;
+int block_mask;
+unsigned char final[32];
+} EVP_CIPHER_CTX;
+
+typedef struct env_md_ctx_st EVP_MD_CTX;
+typedef struct env_md_st EVP_MD;
+
+const EVP_MD *EVP_md5(void);
+const EVP_MD *EVP_sha(void);
+const EVP_MD *EVP_sha1(void);
+const EVP_MD *EVP_sha224(void);
+const EVP_MD *EVP_sha256(void);
+const EVP_MD *EVP_sha384(void);
+const EVP_MD *EVP_sha512(void);
+
+const EVP_CIPHER *EVP_aes_128_ecb(void);
+const EVP_CIPHER *EVP_aes_128_cbc(void);
+const EVP_CIPHER *EVP_aes_128_cfb1(void);
+const EVP_CIPHER *EVP_aes_128_cfb8(void);
+const EVP_CIPHER *EVP_aes_128_cfb128(void);
+const EVP_CIPHER *EVP_aes_128_ofb(void);
+const EVP_CIPHER *EVP_aes_128_ctr(void);
+const EVP_CIPHER *EVP_aes_192_ecb(void);
+const EVP_CIPHER *EVP_aes_192_cbc(void);
+const EVP_CIPHER *EVP_aes_192_cfb1(void);
+const EVP_CIPHER *EVP_aes_192_cfb8(void);
+const EVP_CIPHER *EVP_aes_192_cfb128(void);
+const EVP_CIPHER *EVP_aes_192_ofb(void);
+const EVP_CIPHER *EVP_aes_192_ctr(void);
+const EVP_CIPHER *EVP_aes_256_ecb(void);
+const EVP_CIPHER *EVP_aes_256_cbc(void);
+const EVP_CIPHER *EVP_aes_256_cfb1(void);
+const EVP_CIPHER *EVP_aes_256_cfb8(void);
+const EVP_CIPHER *EVP_aes_256_cfb128(void);
+const EVP_CIPHER *EVP_aes_256_ofb(void);
+
+void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a);
+int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a);
+
+int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
+        ENGINE *impl, unsigned char *key, const unsigned char *iv);
+
+int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
+        const unsigned char *in, int inl);
+
+int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
+
+int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,
+        ENGINE *impl, unsigned char *key, const unsigned char *iv);
+
+int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
+        const unsigned char *in, int inl);
+
+int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
+
+int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md,
+        const unsigned char *salt, const unsigned char *data, int datal,
+        int count, unsigned char *key,unsigned char *iv);
+]]
+
+local ctx_ptr_type = ffi.typeof("EVP_CIPHER_CTX[1]")
+
+hash = {
+    md5 = C.EVP_md5(),
+    sha1 = C.EVP_sha1(),
+    sha224 = C.EVP_sha224(),
+    sha256 = C.EVP_sha256(),
+    sha384 = C.EVP_sha384(),
+    sha512 = C.EVP_sha512()
+}
+
+
+function cipher(size, _cipher)
+    local _size = size or 128
+    local _cipher = _cipher or "cbc"
+    local func = "EVP_aes_" .. _size .. "_" .. _cipher
+    if C[func] then
+        return { size=_size, cipher=_cipher, method=C[func]()}
+    else
+        return nil
+    end
+end
+
+
+function new(self, key, salt, _cipher, _hash, hash_rounds)
+    local encrypt_ctx = ffi_new(ctx_ptr_type)
+    local decrypt_ctx = ffi_new(ctx_ptr_type)
+    local _cipher = _cipher or cipher()
+    local _hash = _hash or hash.md5
+    local hash_rounds = hash_rounds or 1
+    local _cipherLength = _cipher.size/8
+    local gen_key = ffi_new("unsigned char[?]",_cipherLength)
+    local gen_iv = ffi_new("unsigned char[?]",_cipherLength)
+
+    if type(_hash) == "table" then
+        if not _hash.iv or #_hash.iv ~= 16 then
+          return nil
+        end
+
+        if not _hash.method and #key ~= _cipherLength then
+            return nil
+        end
+
+        if _hash.method then
+            local tmp_key = _hash.method(key)
+
+            if #tmp_key ~= _cipherLength then
+                return nil
+            end
+
+            ffi_copy(gen_key, tmp_key, _cipherLength)
+        end
+
+        ffi_copy(gen_iv, _hash.iv, 16)
+    else
+        if C.EVP_BytesToKey(_cipher.method, _hash, salt, key, #key,
+          hash_rounds, gen_key, gen_iv) ~= _cipherLength then
+            return nil
+        end
+    end
+
+
+    C.EVP_CIPHER_CTX_init(encrypt_ctx)
+    C.EVP_CIPHER_CTX_init(decrypt_ctx)
+
+    if C.EVP_EncryptInit_ex(encrypt_ctx, _cipher.method, nil,
+      gen_key, gen_iv) == 0 or
+      C.EVP_DecryptInit_ex(decrypt_ctx, _cipher.method, nil,
+      gen_key, gen_iv) == 0 then
+        return nil
+    end
+
+    ffi_gc(encrypt_ctx, C.EVP_CIPHER_CTX_cleanup)
+    ffi_gc(decrypt_ctx, C.EVP_CIPHER_CTX_cleanup)
+
+    return setmetatable({
+      _encrypt_ctx = encrypt_ctx,
+      _decrypt_ctx = decrypt_ctx
+      }, mt)
+end
+
+
+function encrypt(self, s)
+    local s_len = #s
+    local max_len = s_len + 16
+    local buf = ffi_new("unsigned char[?]", max_len)
+    local out_len = ffi_new("int[1]")
+    local tmp_len = ffi_new("int[1]")
+    local ctx = self._encrypt_ctx
+
+    if C.EVP_EncryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
+        return nil
+    end
+
+    if C.EVP_EncryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
+        return nil
+    end
+
+    if C.EVP_EncryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
+        return nil
+    end
+
+    return ffi_str(buf, out_len[0] + tmp_len[0])
+end
+
+
+function decrypt(self, s)
+    local s_len = #s
+    local buf = ffi_new("unsigned char[?]", s_len)
+    local out_len = ffi_new("int[1]")
+    local tmp_len = ffi_new("int[1]")
+    local ctx = self._decrypt_ctx
+
+    if C.EVP_DecryptInit_ex(ctx, nil, nil, nil, nil) == 0 then
+      return nil
+    end
+
+    if C.EVP_DecryptUpdate(ctx, buf, out_len, s, s_len) == 0 then
+      return nil
+    end
+
+    if C.EVP_DecryptFinal_ex(ctx, buf + out_len[0], tmp_len) == 0 then
+        return nil
+    end
+
+    return ffi_str(buf, out_len[0] + tmp_len[0])
+end
+
+
+-- to prevent use of casual module global variables
+getmetatable(resty.aes).__newindex = function (table, key, val)
+    error('attempt to write to undeclared variable "' .. key .. '": '
+            .. debug.traceback())
+end
+

lualib/resty/dns/resolver.lua

+-- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春)
+
+module("resty.dns.resolver", package.seeall)
+
+
+_VERSION = '0.08'
+
+
+local bit = require "bit"
+
+
+-- local socket = require "socket"
+local class = resty.dns.resolver
+local udp = ngx.socket.udp
+local rand = math.random
+local char = string.char
+local byte = string.byte
+local strlen = string.len
+local find = string.find
+local gsub = string.gsub
+local substr = string.sub
+local format = string.format
+local band = bit.band
+local rshift = bit.rshift
+local lshift = bit.lshift
+local insert = table.insert
+local concat = table.concat
+local re_sub = ngx.re.sub
+local tcp = ngx.socket.tcp
+local log = ngx.log
+local DEBUG = ngx.DEBUG
+
+
+TYPE_A      = 1
+TYPE_NS     = 2
+TYPE_CNAME  = 5
+TYPE_PTR    = 12
+TYPE_MX     = 15
+TYPE_TXT    = 16
+TYPE_AAAA   = 28
+
+CLASS_IN    = 1
+
+
+local resolver_errstrs = {
+    "format error",     -- 1
+    "server failure",   -- 2
+    "name error",       -- 3
+    "not implemented",  -- 4
+    "refused",          -- 5
+}
+
+local mt = { __index = class }
+
+
+function new(class, opts)
+    if not opts then
+        return nil, "no options table specified"
+    end
+
+    local servers = opts.nameservers
+    if not servers or #servers == 0 then
+        return nil, "no nameservers specified"
+    end
+
+    local timeout = opts.timeout or 2000  -- default 2 sec
+
+    local n = #servers
+
+    local socks = {}
+
+    for i = 1, n do
+        local server = servers[i]
+        local sock, err = udp()
+        if not sock then
+            return nil, "failed to create udp socket: " .. err
+        end
+
+        local host, port
+        if type(server) == 'table' then
+            host = server[1]
+            port = server[2] or 53
+
+        else
+            host = server
+            port = 53
+            servers[i] = {host, port}
+        end
+
+        local ok, err = sock:setpeername(host, port)
+        if not ok then
+            return nil, "failed to set peer name: " .. err
+        end
+
+        sock:settimeout(timeout)
+
+        insert(socks, sock)
+    end
+
+    local tcp_sock, err = tcp()
+    if not tcp_sock then
+        return nil, "failed to create tcp socket: " .. err
+    end
+
+    tcp_sock:settimeout(timeout)
+
+    return setmetatable(
+                { cur = rand(1, n), socks = socks,
+                  tcp_sock = tcp_sock,
+                  servers = servers,
+                  retrans = opts.retrans or 5,
+                  no_recurse = opts.no_recurse,
+                }, mt)
+end
+
+
+local function pick_sock(self, socks)
+    local cur = self.cur
+
+    if cur == #socks then
+        self.cur = 1
+    else
+        self.cur = cur + 1
+    end
+
+    return socks[cur]
+end
+
+
+local function get_cur_server(self)
+    local cur = self.cur
+
+    local servers = self.servers
+
+    if cur == 1 then
+        return servers[#servers]
+    end
+
+    return servers[cur - 1]
+end
+
+
+function set_timeout(self, timeout)
+    local socks = self.socks
+    if not socks then
+        return nil, "not initialized"
+    end
+
+    for i = 1, #socks do
+        local sock = socks[i]
+        sock:settimeout(timeout)
+    end
+
+    local tcp_sock = self.tcp_sock
+    if not tcp_sock then
+        return nil, "not initialized"
+    end
+
+    tcp_sock:settimeout(timeout)
+end
+
+
+local function encode_name(s)
+    return char(strlen(s)) .. s
+end
+
+
+local function decode_name(buf, pos)
+    local labels = {}
+    local nptrs = 0
+    local p = pos
+    while nptrs < 128 do
+        local fst = byte(buf, p)
+
+        if not fst then
+            return nil, 'truncated';
+        end
+
+        -- print("fst at ", p, ": ", fst)
+
+        if fst == 0 then
+            if nptrs == 0 then
+                pos = pos + 1
+            end
+            break
+        end
+
+        if band(fst, 0xc0) ~= 0 then
+            -- being a pointer
+            if nptrs == 0 then
+                pos = pos + 2
+            end
+
+            nptrs = nptrs + 1
+
+            local snd = byte(buf, p + 1)
+            if not snd then
+                return nil, 'truncated'
+            end
+
+            p = lshift(band(fst, 0x3f), 8) + snd + 1
+
+            -- print("resolving ptr ", p, ": ", byte(buf, p))
+
+        else
+            -- being a label
+            local label = substr(buf, p + 1, p + fst)
+            insert(labels, label)
+
+            -- print("resolved label ", label)
+
+            p = p + fst + 1
+
+            if nptrs == 0 then
+                pos = p
+            end
+        end
+    end
+
+    return concat(labels, "."), pos
+end
+
+
+local function build_request(qname, id, no_recurse, opts)
+    local qtype
+
+    if opts then
+        qtype = opts.qtype
+    end
+
+    if not qtype then
+        qtype = 1  -- A record
+    end
+
+    local ident_hi = char(rshift(id, 8))
+    local ident_lo = char(band(id, 0xff))
+
+    local flags
+    if no_recurse then
+        print("found no recurse")
+        flags = "\0\0"
+    else
+        flags = "\1\0"
+    end
+
+    local nqs = "\0\1"
+    local nan = "\0\0"
+    local nns = "\0\0"
+    local nar = "\0\0"
+    local typ = "\0" .. char(qtype)
+    local class = "\0\1"    -- the Internet class
+
+    local name = gsub(qname, "([^.]+)%.?", encode_name) .. '\0'
+
+    return {
+        ident_hi, ident_lo, flags, nqs, nan, nns, nar,
+        name, typ, class
+    }
+end
+
+
+local function parse_response(buf, id)
+    local n = strlen(buf)
+    if n < 12 then
+        return nil, 'truncated';
+    end
+
+    -- header layout: ident flags nqs nan nns nar
+
+    local ident_hi = byte(buf, 1)
+    local ident_lo = byte(buf, 2)
+    local ans_id = lshift(ident_hi, 8) + ident_lo
+
+    -- print("id: ", id, ", ans id: ", ans_id)
+
+    if ans_id ~= id then
+        -- identifier mismatch and throw it away
+        log(DEBUG, "id mismatch in the DNS reply: ", ans_id, " ~= ", id)
+        return nil, "id mismatch"
+    end
+
+    local flags_hi = byte(buf, 3)
+    local flags_lo = byte(buf, 4)
+    local flags = lshift(flags_hi, 8) + flags_lo
+
+    -- print(format("flags: 0x%x", flags))
+
+    if band(flags, 0x8000) == 0 then
+        return nil, format("bad QR flag in the DNS response")
+    end
+
+    if band(flags, 0x200) ~= 0 then
+        return nil, "truncated"
+    end
+
+    local code = band(flags, 0x7f)
+
+    -- print(format("code: %d", code))
+
+    if code ~= 0 then
+        return nil, format("server returned code %d: %s", code,
+                           resolver_errstrs[code] or "unknown")
+    end
+
+    local nqs_hi = byte(buf, 5)
+    local nqs_lo = byte(buf, 6)
+    local nqs = lshift(nqs_hi, 8) + nqs_lo
+
+    -- print("nqs: ", nqs)
+
+    if nqs ~= 1 then
+        return nil, format("bad number of questions in DNS response: %d", nqs)
+    end
+
+    local nan_hi = byte(buf, 7)
+    local nan_lo = byte(buf, 8)
+    local nan = lshift(nan_hi, 8) + nan_lo
+
+    -- print("nan: ", nan)
+
+    -- skip the question part
+
+    local ans_qname, pos = decode_name(buf, 13)
+    if not ans_qname then
+        return nil, pos
+    end
+
+    -- print("qname in reply: ", ans_qname)
+
+    -- print("question: ", substr(buf, 13, pos))
+
+    if pos + 3 + nan * 12 > n then
+        -- print(format("%d > %d", pos + 3 + nan * 12, n))
+        return nil, 'truncated';
+    end
+
+    -- question section layout: qname qtype(2) qclass(2)
+
+    local type_hi = byte(buf, pos)
+    local type_lo = byte(buf, pos + 1)
+    local ans_type = lshift(type_hi, 8) + type_lo
+
+    -- print("ans qtype: ", ans_type)
+
+    local class_hi = byte(buf, pos + 2)
+    local class_lo = byte(buf, pos + 3)
+    local qclass = lshift(class_hi, 8) + class_lo
+
+    -- print("ans qclass: ", qclass)
+
+    if qclass ~= 1 then
+        return nil, format("unknown query class %d in DNS response", qclass)
+    end
+
+    pos = pos + 4
+
+    local answers = {}
+
+    for i = 1, nan do
+        -- print(format("ans %d: qtype:%d qclass:%d", i, qtype, qclass))
+
+        local ans = {}
+        insert(answers, ans)
+
+        local name
+        name, pos = decode_name(buf, pos)
+        if not name then
+            return nil, pos
+        end
+
+        ans.name = name
+
+        -- print("name: ", name)
+
+        type_hi = byte(buf, pos)
+        type_lo = byte(buf, pos + 1)
+        local typ = lshift(type_hi, 8) + type_lo
+
+        ans.type = typ
+
+        -- print("type: ", typ)
+
+        class_hi = byte(buf, pos + 2)
+        class_lo = byte(buf, pos + 3)
+        local class = lshift(class_hi, 8) + class_lo
+
+        ans.class = class
+
+        -- print("class: ", class)
+
+        local ttl_bytes = { byte(buf, pos + 4, pos + 7) }
+
+        -- print("ttl bytes: ", concat(ttl_bytes, " "))
+
+        local ttl = lshift(ttl_bytes[1], 24) + lshift(ttl_bytes[2], 16)
+                    + lshift(ttl_bytes[3], 8) + ttl_bytes[4]
+
+        -- print("ttl: ", ttl)
+
+        ans.ttl = ttl
+
+        local len_hi = byte(buf, pos + 8)
+        local len_lo = byte(buf, pos + 9)
+        local len = lshift(len_hi, 8) + len_lo
+
+        -- print("len: ", len)
+
+        pos = pos + 10
+
+        if typ == TYPE_A then
+
+            if len ~= 4 then
+                return nil, "bad A record value length: " .. len
+            end
+
+            local addr_bytes = { byte(buf, pos, pos + 3) }
+            local addr = concat(addr_bytes, ".")
+            -- print("ipv4 address: ", addr)
+
+            ans.address = addr
+
+            pos = pos + 4
+
+        elseif typ == TYPE_CNAME then
+
+            local cname, p = decode_name(buf, pos)
+            if not cname then
+                return nil, pos
+            end
+
+            if p - pos ~= len then
+                return nil, format("bad cname record length: %d ~= %d",
+                                   p - pos, len)
+            end
+
+            pos = p
+
+            -- print("cname: ", cname)
+
+            ans.cname = cname
+
+        elseif typ == TYPE_AAAA then
+
+            if len ~= 16 then
+                return nil, "bad AAAA record value length: " .. len
+            end
+
+            local addr_bytes = { byte(buf, pos, pos + 15) }
+            local flds = {}
+            local comp_begin, comp_end
+            for i = 1, 16, 2 do
+                local a = addr_bytes[i]
+                local b = addr_bytes[i + 1]
+                if a == 0 then
+                    insert(flds, format("%x", b))
+
+                else
+                    insert(flds, format("%x%02x", a, b))
+                end
+            end
+
+            -- we do not compress the IPv6 addresses by default
+            --  due to performance considerations
+
+            ans.address = concat(flds, ":")
+
+            pos = pos + 16
+
+        elseif typ == TYPE_MX then
+
+            -- print("len = ", len)
+
+            if len < 3 then
+                return nil, "bad MX record value length: " .. len
+            end
+
+            local pref_hi = byte(buf, pos)
+            local pref_lo = byte(buf, pos + 1)
+
+            ans.preference = lshift(pref_hi, 8) + pref_lo
+
+            local host, p = decode_name(buf, pos + 2)
+            if not host then
+                return nil, pos
+            end
+
+            if p - pos ~= len then
+                return nil, format("bad cname record length: %d ~= %d",
+                                   p - pos, len)
+            end
+
+            ans.exchange = host
+
+            pos = p
+
+        elseif typ == TYPE_NS then
+
+            local name, p = decode_name(buf, pos)
+            if not name then
+                return nil, pos
+            end
+
+            if p - pos ~= len then
+                return nil, format("bad cname record length: %d ~= %d",
+                                   p - pos, len)
+            end
+
+            pos = p
+
+            -- print("name: ", name)
+
+            ans.nsdname = name
+
+        elseif typ == TYPE_TXT then
+
+            ans.txt = substr(buf, pos, pos + len - 1)
+            pos = pos + len
+
+        elseif typ == TYPE_PTR then
+
+            local name, p = decode_name(buf, pos)
+            if not name then
+                return nil, pos
+            end
+
+            if p - pos ~= len then
+                return nil, format("bad cname record length: %d ~= %d",
+                                   p - pos, len)
+            end
+
+            pos = p
+
+            -- print("name: ", name)
+
+            ans.ptrdname = name
+
+        else
+            -- for unknown types, just forward the raw value
+
+            ans.rdata = substr(buf, pos, pos + len - 1)
+            pos = pos + len
+        end
+    end
+
+    return answers
+end
+
+
+local function gen_id(self)
+    local id = self._id   -- for regression testing
+    if id then
+        return id
+    end
+    return rand(0, 65535)   -- two bytes
+end
+
+
+local function _tcp_query(self, query, id)
+    local sock = self.tcp_sock
+    if not sock then
+        return "not initialized"
+    end
+
+    log(DEBUG, "query the TCP server due to reply truncation")
+
+    local server = get_cur_server(self)
+
+    local ok, err = sock:connect(server[1], server[2])
+    if not ok then
+        return nil, "failed to connect to TCP server "
+            .. concat(server, ":") .. ": " .. err
+    end
+
+    query = concat(query, "")
+    local len = strlen(query)
+
+    local len_hi = char(rshift(len, 8))
+    local len_lo = char(band(len, 0xff))
+
+    local bytes, err = sock:send({len_hi, len_lo, query})
+    if not bytes then
+        return nil, "failed to send query to TCP server "
+            .. concat(server, ":") .. ": " .. err
+    end
+
+    local buf, err = sock:receive(2)
+    if not buf then
+        return nil, "failed to receive the reply length field from TCP server "
+            .. concat(server, ":") .. ": " .. err
+    end
+
+    local len_hi = byte(buf, 1)
+    local len_lo = byte(buf, 2)
+    local len = lshift(len_hi, 8) + len_lo
+
+    -- print("tcp message len: ", len)
+
+    buf, err = sock:receive(len)
+    if not buf then
+        return nil, "failed to receive the reply message body from TCP server "
+            .. concat(server, ":") .. ": " .. err
+    end
+
+    local answers, err = parse_response(buf, id)
+    if not answers then
+        return nil, "failed to parse the reply from the TCP server "
+            .. concat(server, ":") .. ": " .. err
+    end
+
+    sock:close()
+
+    return answers
+end
+
+
+function tcp_query(self, qname, opts)
+    local socks = self.socks
+    if not socks then
+        return nil, nil, "not initialized"
+    end
+
+    pick_sock(self, socks)
+
+    local id = gen_id(self)
+
+    local query = build_request(qname, id, self.no_recurse, opts)
+
+    return _tcp_query(self, query, id)
+end
+
+
+function query(self, qname, opts)
+    local socks = self.socks
+    if not socks then
+        return nil, nil, "not initialized"
+    end
+
+    local id = gen_id(self)
+
+    local query = build_request(qname, id, self.no_recurse, opts)
+
+    -- local cjson = require "cjson"
+    -- print("query: ", cjson.encode(concat(query, "")))
+
+    local retrans = self.retrans
+
+    -- print("retrans: ", retrans)
+
+    for i = 1, retrans do
+        local sock = pick_sock(self, socks)
+
+        local ok, err = sock:send(query)
+        if not ok then
+            local server = get_cur_server(self)
+            return nil, "failed to send request to UDP server "
+                .. concat(server, ":") .. ": " .. err
+        end
+
+        local buf, err
+
+        for j = 1, 128 do
+            buf, err = sock:receive(4096)
+
+            if err then
+                break
+            end
+
+            if buf then
+                local answers
+                answers, err = parse_response(buf, id)
+                if not answers then
+                    if err == "truncated" then
+                        return _tcp_query(self, query, id)
+                    end
+
+                    if err ~= "id mismatch" then
+                        return nil, err
+                    end
+
+                    -- retry receiving when err == "id mismatch"
+                else
+                    return answers
+                end
+            end
+        end
+
+        if err ~= "timeout" or i == retrans then
+            local server = get_cur_server(self)
+            return nil, "failed to receive reply from UDP server "
+                .. concat(server, ":") .. ": " .. err
+        end
+    end
+
+    -- impossible to reach here
+end
+
+
+function compress_ipv6_addr(addr)
+    local addr = re_sub(addr, "^(0:)+|(:0)+$|:(0:)+", "::", "jo")
+    if addr == "::0" then
+        addr = "::"
+    end
+
+    return addr
+end
+
+
+math.randomseed(ngx.time())
+
+
+-- to prevent use of casual module global variables
+getmetatable(class).__newindex = function (table, key, val)
+    error('attempt to write to undeclared variable "' .. key .. '": '
+            .. debug.traceback())
+end
+

lualib/resty/md5.lua

+module("resty.md5", package.seeall)
+
+_VERSION = '0.06'
+
+local ffi = require "ffi"
+local ffi_new = ffi.new
+local ffi_str = ffi.string
+local C = ffi.C
+
+local mt = { __index = resty.md5 }
+
+
+ffi.cdef[[
+typedef unsigned long MD5_LONG ;
+
+enum {
+    MD5_CBLOCK = 64,
+    MD5_LBLOCK = MD5_CBLOCK/4
+};
+
+typedef struct MD5state_st
+        {
+        MD5_LONG A,B,C,D;
+        MD5_LONG Nl,Nh;
+        MD5_LONG data[MD5_LBLOCK];
+        unsigned int num;
+        } MD5_CTX;
+
+int MD5_Init(MD5_CTX *c);
+int MD5_Update(MD5_CTX *c, const void *data, size_t len);
+int MD5_Final(unsigned char *md, MD5_CTX *c);
+]]
+
+local buf = ffi_new("char[16]")
+local ctx_ptr_type = ffi.typeof("MD5_CTX[1]")
+
+
+function new(self)
+    local ctx = ffi_new(ctx_ptr_type)
+    if C.MD5_Init(ctx) == 0 then
+        return nil
+    end
+
+    return setmetatable({ _ctx = ctx }, mt)
+end
+
+
+function update(self, s)
+    return C.MD5_Update(self._ctx, s, #s) == 1
+end
+
+
+function final(self)
+    if C.MD5_Final(buf, self._ctx) == 1 then
+        return ffi_str(buf, 16)
+    end
+
+    return nil
+end
+
+
+function reset(self)
+    return C.MD5_Init(self._ctx) == 1
+end
+
+
+-- to prevent use of casual module global variables
+getmetatable(resty.md5).__newindex = function (table, key, val)
+    error('attempt to write to undeclared variable "' .. key .. '": '
+            .. debug.traceback())
+end
+

lualib/resty/memcached.lua

+-- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春)
+
+module("resty.memcached", package.seeall)
+
+
+_VERSION = '0.09'
+
+
+local sub = string.sub
+local escape_uri = ngx.escape_uri
+local unescape_uri = ngx.unescape_uri
+local match = string.match
+local tcp = ngx.socket.tcp
+local strlen = string.len
+local insert = table.insert
+local concat = table.concat
+
+
+local class = resty.memcached
+
+local mt = { __index = class }
+
+
+function new(self, opts)
+    local sock, err = tcp()
+    if not sock then
+        return nil, err
+    end
+
+    local escape_key = escape_uri
+    local unescape_key = unescape_uri
+
+    if opts then
+       local key_transform = opts.key_transform
+
+       if key_transform then
+          escape_key = key_transform[1]
+          unescape_key = key_transform[2]
+          if not escape_key or not unescape_key then
+             return nil, "expecting key_transform = { escape, unescape } table"
+          end
+       end
+    end
+
+    return setmetatable({
+        sock = sock,
+        escape_key = escape_key,
+        unescape_key = unescape_key,
+    }, mt)
+end
+
+
+function set_timeout(self, timeout)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:settimeout(timeout)
+end
+
+
+function connect(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:connect(...)
+end
+
+
+function get(self, key)
+    if type(key) == "table" then
+        return _multi_get(self, key)
+    end
+
+    local sock = self.sock
+    if not sock then
+        return nil, nil, "not initialized"
+    end
+
+    local cmd = {"get ", self.escape_key(key), "\r\n"}
+    local bytes, err = sock:send(concat(cmd))
+    if not bytes then
+        return nil, nil, "failed to send command: " .. (err or "")
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, nil, "failed to receive 1st line: " .. (err or "")
+    end
+
+    if line == 'END' then
+        return nil, nil, nil
+    end
+
+    local flags, len = match(line, '^VALUE %S+ (%d+) (%d+)$')
+    if not flags then
+        return nil, nil, "bad line: " .. line
+    end
+
+    -- print("len: ", len, ", flags: ", flags)
+
+    local data, err = sock:receive(len)
+    if not data then
+        return nil, nil, "failed to receive data chunk: " .. (err or "")
+    end
+
+    line, err = sock:receive(2) -- discard the trailing CRLF
+    if not line then
+        return nil, nil, "failed to receive CRLF: " .. (err or "")
+    end
+
+    line, err = sock:receive() -- discard "END\r\n"
+    if not line then
+        return nil, nil, "failed to receive END CRLF: " .. (err or "")
+    end
+
+    return data, flags
+end
+
+
+function _multi_get(self, keys)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local nkeys = #keys
+
+    if nkeys == 0 then
+        return {}, nil
+    end
+
+    local escape_key = self.escape_key
+    local cmd = {"get"}
+
+    for i = 1, nkeys do
+        insert(cmd, " ")
+        insert(cmd, escape_key(keys[i]))
+    end
+    insert(cmd, "\r\n")
+
+    -- print("multi get cmd: ", cmd)
+
+    local bytes, err = sock:send(concat(cmd))
+    if not bytes then
+        return nil, err
+    end
+
+    local unescape_key = self.unescape_key
+    local results = {}
+
+    while true do
+        local line, err = sock:receive()
+        if not line then
+            return nil, err
+        end
+
+        if line == 'END' then
+            break
+        end
+
+        local key, flags, len = match(line, '^VALUE (%S+) (%d+) (%d+)$')
+        -- print("key: ", key, "len: ", len, ", flags: ", flags)
+
+        if key then
+
+            local data, err = sock:receive(len)
+            if not data then
+                return nil, err
+            end
+
+            results[unescape_key(key)] = {data, flags}
+
+            data, err = sock:receive(2) -- discard the trailing CRLF
+            if not data then
+                return nil, err
+            end
+        end
+    end
+
+    return results
+end
+
+
+function gets(self, key)
+    if type(key) == "table" then
+        return _multi_gets(self, key)
+    end
+
+    local sock = self.sock
+    if not sock then
+        return nil, nil, nil, "not initialized"
+    end
+
+    local cmd = {"gets ", self.escape_key(key), "\r\n"}
+    local bytes, err = sock:send(concat(cmd))
+    if not bytes then
+        return nil, nil, err
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, nil, nil, err
+    end
+
+    if line == 'END' then
+        return nil, nil, nil, nil
+    end
+
+    local flags, len, cas_uniq = match(line, '^VALUE %S+ (%d+) (%d+) (%d+)$')
+    if not flags then
+        return nil, nil, nil, line
+    end
+
+    -- print("len: ", len, ", flags: ", flags)
+
+    local data, err = sock:receive(len)
+    if not data then
+        return nil, nil, nil, err
+    end
+
+    line, err = sock:receive(2) -- discard the trailing CRLF
+    if not line then
+        return nil, nil, nil, err
+    end
+
+    line, err = sock:receive() -- discard "END\r\n"
+    if not line then
+        return nil, nil, nil, err
+    end
+
+    return data, flags, cas_uniq
+end
+
+
+function _multi_gets(self, keys)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local nkeys = #keys
+
+    if nkeys == 0 then
+        return {}, nil
+    end
+
+    local escape_key = self.escape_key
+    local cmd = {"gets"}
+
+    for i = 1, nkeys do
+        insert(cmd, " ")
+        insert(cmd, escape_key(keys[i]))
+    end
+    insert(cmd, "\r\n")
+
+    -- print("multi get cmd: ", cmd)
+
+    local bytes, err = sock:send(concat(cmd))
+    if not bytes then
+        return nil, err
+    end
+
+    local unescape_key = self.unescape_key
+    local results = {}
+
+    while true do
+        local line, err = sock:receive()
+        if not line then
+            return nil, err
+        end
+
+        if line == 'END' then
+            break
+        end
+
+        local key, flags, len, cas_uniq =
+                match(line, '^VALUE (%S+) (%d+) (%d+) (%d+)$')
+
+        -- print("key: ", key, "len: ", len, ", flags: ", flags)
+
+        if key then
+
+            local data, err = sock:receive(len)
+            if not data then
+                return nil, err
+            end
+
+            results[unescape_key(key)] = {data, flags, cas_uniq}
+
+            data, err = sock:receive(2) -- discard the trailing CRLF
+            if not data then
+                return nil, err
+            end
+        end
+    end
+
+    return results
+end
+
+
+function set(self, ...)
+    return _store(self, "set", ...)
+end
+
+
+function add(self, ...)
+    return _store(self, "add", ...)
+end
+
+
+function replace(self, ...)
+    return _store(self, "replace", ...)
+end
+
+
+function append(self, ...)
+    return _store(self, "append", ...)
+end
+
+
+function prepend(self, ...)
+    return _store(self, "prepend", ...)
+end
+
+
+local function _expand_table(value)
+    local segs = {}
+    local nelems = #value
+    for i = 1, nelems do
+        local seg = value[i]
+        if type(seg) == "table" then
+            insert(segs, _expand_table(seg))
+        else
+            insert(segs, seg)
+        end
+    end
+    return concat(segs)
+end
+
+
+function _store(self, cmd, key, value, exptime, flags)
+    if not exptime then
+        exptime = 0
+    end
+
+    if not flags then
+        flags = 0
+    end
+
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    if type(value) == "table" then
+        value = _expand_table(value)
+    end
+
+    local req = {cmd, " ", self.escape_key(key), " ", flags, " ", exptime, " ",
+                 strlen(value), "\r\n", value, "\r\n"}
+
+    local bytes, err = sock:send(concat(req))
+    if not bytes then
+        return nil, err
+    end
+
+    local data, err = sock:receive()
+    if not data then
+        return nil, err
+    end
+
+    if data == "STORED" then
+        return 1
+    end
+
+    return nil, data
+end
+
+
+function cas(self, key, value, cas_uniq, exptime, flags)
+    if not exptime then
+        exptime = 0
+    end
+
+    if not flags then
+        flags = 0
+    end
+
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local req = {"cas ", self.escape_key(key), " ", flags, " ", exptime, " ",
+                 strlen(value), " ", cas_uniq, "\r\n", value, "\r\n"}
+
+    -- local cjson = require "cjson"
+    -- print("request: ", cjson.encode(req))
+
+    local bytes, err = sock:send(concat(req))
+    if not bytes then
+        return nil, err
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, err
+    end
+
+    -- print("response: [", line, "]")
+
+    if line == "STORED" then
+        return 1
+    end
+
+    return nil, line
+end
+
+
+function delete(self, key)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    key = self.escape_key(key)
+
+    local req = {"delete ", key, "\r\n"}
+
+    local bytes, err = sock:send(concat(req))
+    if not bytes then
+        return nil, err
+    end
+
+    local res, err = sock:receive()
+    if not res then
+        return nil, err
+    end
+
+    if res ~= 'DELETED' then
+        return nil, res
+    end
+
+    return 1
+end
+
+
+function set_keepalive(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:setkeepalive(...)
+end
+
+
+function get_reused_times(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:getreusedtimes()
+end
+
+
+function flush_all(self, time)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local req
+    if time then
+        req = concat({"flush_all ", time, "\r\n"})
+    else
+        req = "flush_all\r\n"
+    end
+
+    local bytes, err = sock:send(req)
+    if not bytes then
+        return nil, err
+    end
+
+    local res, err = sock:receive()
+    if not res then
+        return nil, err
+    end
+
+    if res ~= 'OK' then
+        return nil, res
+    end
+
+    return 1
+end
+
+
+function _incr_decr(self, cmd, key, value)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local req = {cmd, " ", self.escape_key(key), " ", value, "\r\n"}
+
+    local bytes, err = sock:send(concat(req))
+    if not bytes then
+        return nil, err
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, err
+    end
+
+    if not match(line, '^%d+$') then
+        return nil, line
+    end
+
+    return line
+end
+
+
+function incr(self, key, value)
+    return _incr_decr(self, "incr", key, value)
+end
+
+
+function decr(self, key, value)
+    return _incr_decr(self, "decr", key, value)
+end
+
+
+function stats(self, args)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local req
+    if args then
+        req = concat({"stats ", args, "\r\n"})
+    else
+        req = "stats\r\n"
+    end
+
+    local bytes, err = sock:send(req)
+    if not bytes then
+        return nil, err
+    end
+
+    local lines = {}
+    while true do
+        local line, err = sock:receive()
+        if not line then
+            return nil, err
+        end
+
+        if line == 'END' then
+            return lines, nil
+        end
+
+        if not match(line, "ERROR") then
+            insert(lines, line)
+        else
+            return nil, line
+        end
+    end
+
+    -- cannot reach here...
+    return lines
+end
+
+
+function version(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local bytes, err = sock:send("version\r\n")
+    if not bytes then
+        return nil, err
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, err
+    end
+
+    local ver = match(line, "^VERSION (.+)$")
+    if not ver then
+        return nil, ver
+    end
+
+    return ver
+end
+
+
+function quit(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local bytes, err = sock:send("quit\r\n")
+    if not bytes then
+        return nil, err
+    end
+
+    return 1
+end
+
+
+function verbosity(self, level)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local bytes, err = sock:send(concat({"verbosity ", level, "\r\n"}))
+    if not bytes then
+        return nil, err
+    end
+
+    local line, err = sock:receive()
+    if not line then
+        return nil, err
+    end
+
+    if line ~= 'OK' then
+        return nil, line
+    end
+
+    return 1
+end
+
+
+function close(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:close()
+end
+
+
+-- to prevent use of casual module global variables
+getmetatable(class).__newindex = function (table, key, val)
+    error('attempt to write to undeclared variable "' .. key .. '"')
+end
+

lualib/resty/mysql.lua

+-- Copyright (C) 2012 Zhang "agentzh" Yichun (章亦春)
+
+module("resty.mysql", package.seeall)
+
+_VERSION = '0.10'
+
+local bit = require "bit"
+
+
+-- constants
+
+local STATE_CONNECTED = 1
+local STATE_COMMAND_SENT = 2
+
+local COM_QUERY = 0x03
+
+local SERVER_MORE_RESULTS_EXISTS = 8
+
+
+-- global variables
+
+local mt = { __index = resty.mysql }
+
+local sub = string.sub
+local tcp = ngx.socket.tcp
+local insert = table.insert
+local strlen = string.len
+local strbyte = string.byte
+local strchar = string.char
+local strfind = string.find
+local strrep = string.rep
+local null = ngx.null
+local band = bit.band
+local bxor = bit.bxor
+local bor = bit.bor
+local lshift = bit.lshift
+local rshift = bit.rshift
+local tohex = bit.tohex
+local sha1 = ngx.sha1_bin
+local concat = table.concat
+
+
+-- mysql field value type converters
+local converters = {}
+
+for i = 0x01, 0x05 do
+    -- tiny, short, long, float, double
+    converters[i] = tonumber
+end
+-- converters[0x08] = tonumber  -- long long
+converters[0x09] = tonumber  -- int24
+converters[0x0d] = tonumber  -- year
+
+
+local function _get_byte2(data, i)
+    local a, b = strbyte(data, i, i + 1)
+    return bor(a, lshift(b, 8)), i + 2
+end
+
+
+local function _get_byte3(data, i)
+    local a, b, c = strbyte(data, i, i + 2)
+    return bor(a, lshift(b, 8), lshift(c, 16)), i + 3
+end
+
+
+local function _get_byte4(data, i)
+    local a, b, c, d = strbyte(data, i, i + 3)
+    return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24)), i + 4
+end
+
+
+local function _get_byte8(data, i)
+    local a, b, c, d, e, f, g, h = strbyte(data, i, i + 7)
+    return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24), lshift(e, 32),
+               lshift(f, 40), lshift(g, 48), lshift(h, 56)), i + 8
+end
+
+
+local function _set_byte2(n)
+    return strchar(band(n, 0xff), band(rshift(n, 8), 0xff))
+end
+
+
+local function _set_byte3(n)
+    return strchar(band(n, 0xff), band(rshift(n, 8), 0xff),
+        band(rshift(n, 16), 0xff))
+end
+
+
+local function _set_byte4(n)
+    return strchar(band(n, 0xff), band(rshift(n, 8), 0xff),
+        band(rshift(n, 16), 0xff), band(rshift(n, 24), 0xff))
+end
+
+
+local function _from_cstring(data, i)
+    local last = strfind(data, "\0", i, true)
+    if not last then
+        return nil, nil
+    end
+
+    return sub(data, i, last), last + 1
+end
+
+
+local function _to_cstring(data)
+    return {data, "\0"}
+end
+
+
+local function _to_binary_coded_string(data)
+    return {strchar(strlen(data)), data}
+end
+
+
+local function _dump(data)
+    local bytes = {}
+    for i = 1, #data do
+        insert(bytes, strbyte(data, i, i))
+    end
+    return concat(bytes, " ")
+end
+
+
+local function _dumphex(data)
+    local bytes = {}
+    for i = 1, #data do
+        insert(bytes, tohex(strbyte(data, i), 2))
+    end
+    return concat(bytes, " ")
+end
+
+
+local function _compute_token(password, scramble)
+    if password == "" then
+        return ""
+    end
+
+    local stage1 = sha1(password)
+    local stage2 = sha1(stage1)
+    local stage3 = sha1(scramble .. stage2)
+    local bytes = {}
+    for i = 1, #stage1 do
+         insert(bytes,
+             bxor(strbyte(stage3, i), strbyte(stage1, i)))
+    end
+
+    return strchar(unpack(bytes))
+end
+
+
+function _send_packet(self, req, size)
+    local sock = self.sock
+
+    self.packet_no = self.packet_no + 1
+
+    --print("packet no: ", self.packet_no)
+
+    local packet = {
+        _set_byte3(size),
+        strchar(self.packet_no),
+        req
+    }
+
+    --print("sending packet...")
+
+    return sock:send(packet)
+end
+
+
+function _recv_packet(self)
+    local sock = self.sock
+
+    local data, err = sock:receive(4) -- packet header
+    if not data then
+        return nil, nil, "failed to receive packet header: " .. err
+    end
+
+    --print("packet header: ", _dump(data))
+
+    local len, pos = _get_byte3(data, 1)
+
+    --print("packet length: ", len)
+
+    if len == 0 then
+        return nil, nil, "empty packet"
+    end
+
+    if len > self._max_packet_size then
+        return nil, nil, "packet size too big: " .. len
+    end
+
+    local num = strbyte(data, pos)
+
+    --print("recv packet: packet no: ", num)
+
+    self.packet_no = num
+
+    data, err = sock:receive(len)
+
+    --print("receive returned")
+
+    if not data then
+        return nil, nil, "failed to read packet content: " .. err
+    end
+
+    --print("packet content: ", _dump(data))
+    --print("packet content (ascii): ", data)
+
+    local field_count = strbyte(data, 1)
+
+    local typ
+    if field_count == 0x00 then
+        typ = "OK"
+    elseif field_count == 0xff then
+        typ = "ERR"
+    elseif field_count == 0xfe then
+        typ = "EOF"
+    elseif field_count <= 250 then
+        typ = "DATA"
+    end
+
+    return data, typ
+end
+
+
+local function _from_length_coded_bin(data, pos)
+    local first = strbyte(data, pos)
+
+    --print("LCB: first: ", first)
+
+    if not first then
+        return nil, pos
+    end
+
+    if first >= 0 and first <= 250 then
+        return first, pos + 1
+    end
+
+    if first == 251 then
+        return null, pos + 1
+    end
+
+    if first == 252 then
+        pos = pos + 1
+        return _get_byte2(data, pos)
+    end
+
+    if first == 253 then
+        pos = pos + 1
+        return _get_byte3(data, pos)
+    end
+
+    if first == 254 then
+        pos = pos + 1
+        return _get_byte8(data, pos)
+    end
+
+    return false, pos + 1
+end
+
+
+local function _from_length_coded_str(data, pos)
+    local len
+    len, pos = _from_length_coded_bin(data, pos)
+    if len == nil or len == null then
+        return null, pos
+    end
+
+    return sub(data, pos, pos + len - 1), pos + len
+end
+
+
+local function _parse_ok_packet(packet)
+    local res = {}
+    local pos
+
+    res.affected_rows, pos = _from_length_coded_bin(packet, 2)
+
+    --print("affected rows: ", res.affected_rows, ", pos:", pos)
+
+    res.insert_id, pos = _from_length_coded_bin(packet, pos)
+
+    --print("insert id: ", res.insert_id, ", pos:", pos)
+
+    res.server_status, pos = _get_byte2(packet, pos)
+
+    --print("server status: ", res.server_status, ", pos:", pos)
+
+    res.warning_count, pos = _get_byte2(packet, pos)
+
+    --print("warning count: ", res.warning_count, ", pos: ", pos)
+
+    local message = sub(packet, pos)
+    if message and message ~= "" then
+        res.message = message
+    end
+
+    --print("message: ", res.message, ", pos:", pos)
+
+    return res
+end
+
+
+local function _parse_eof_packet(packet)
+    local pos = 2
+
+    local warning_count, pos = _get_byte2(packet, pos)
+    local status_flags = _get_byte2(packet, pos)
+
+    return warning_count, status_flags
+end
+
+
+local function _parse_err_packet(packet)
+    local errno, pos = _get_byte2(packet, 2)
+    local marker = sub(packet, pos, pos)
+    local sqlstate
+    if marker == '#' then
+        -- with sqlstate
+        pos = pos + 1
+        sqlstate = sub(packet, pos, pos + 5 - 1)
+        pos = pos + 5
+    end
+
+    local message = sub(packet, pos)
+    return errno, message, sqlstate
+end
+
+
+local function _parse_result_set_header_packet(packet)
+    local field_count, pos = _from_length_coded_bin(packet, 1)
+
+    local extra
+    extra = _from_length_coded_bin(packet, pos)
+
+    return field_count, extra
+end
+
+
+local function _parse_field_packet(data)
+    local col = {}
+    local catalog, db, table, orig_table, orig_name, charsetnr, length
+    local pos
+    catalog, pos = _from_length_coded_str(data, 1)
+
+    --print("catalog: ", col.catalog, ", pos:", pos)
+
+    db, pos = _from_length_coded_str(data, pos)
+    table, pos = _from_length_coded_str(data, pos)
+    orig_table, pos = _from_length_coded_str(data, pos)
+    col.name, pos = _from_length_coded_str(data, pos)
+
+    orig_name, pos = _from_length_coded_str(data, pos)
+
+    pos = pos + 1 -- ignore the filler
+
+    charsetnr, pos = _get_byte2(data, pos)
+
+    length, pos = _get_byte4(data, pos)
+
+    col.type = strbyte(data, pos)
+
+    --[[
+    pos = pos + 1
+
+    col.flags, pos = _get_byte2(data, pos)
+
+    col.decimals = strbyte(data, pos)
+    pos = pos + 1
+
+    local default = sub(data, pos + 2)
+    if default and default ~= "" then
+        col.default = default
+    end
+    --]]
+
+    return col
+end
+
+
+local function _parse_row_data_packet(data, cols, compact)
+    local row = {}
+    local pos = 1
+    for i = 1, #cols do
+        local value
+        value, pos = _from_length_coded_str(data, pos)
+        local col = cols[i]
+        local typ = col.type
+        local name = col.name
+
+        --print("row field value: ", value, ", type: ", typ)
+
+        if value ~= null then
+            local conv = converters[typ]
+            if conv then
+                value = conv(value)
+            end
+            -- insert(row, value)
+        end
+
+        if compact then
+            insert(row, value)
+        else
+            row[name] = value
+        end
+    end
+
+    return row
+end
+
+
+local function _recv_field_packet(self)
+    local packet, typ, err = _recv_packet(self)
+    if not packet then
+        return nil, err
+    end
+
+    if typ == "ERR" then
+        local errno, msg, sqlstate = _parse_err_packet(packet)
+        return nil, msg, errno, sqlstate
+    end
+
+    if typ ~= 'DATA' then
+        return nil, "bad field packet type: " .. typ
+    end
+
+    -- typ == 'DATA'
+
+    return _parse_field_packet(packet)
+end
+
+
+function new(self)
+    return setmetatable({ sock = tcp() }, mt)
+end
+
+
+function set_timeout(self, timeout)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:settimeout(timeout)
+end
+
+
+function connect(self, opts)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    local max_packet_size = opts.max_packet_size
+    if not max_packet_size then
+        max_packet_size = 1024 * 1024 -- default 1 MB
+    end
+    self._max_packet_size = max_packet_size
+
+    local ok, err
+
+    self.compact = opts.compact_arrays
+
+    local database = opts.database or ""
+    local user = opts.user or ""
+
+    local pool = opts.pool
+
+    local host = opts.host