Просмотр исходного кода

add: 添加protobuf库,基于https://github.com/starwing/lua-protobuf, 做了少许改造

仅在air101验证过, 由于是工具库, 理论上任意环境均可使用
Wendal Chen 3 лет назад
Родитель
Сommit
e2fda4ff78

+ 21 - 0
components/serialization/protobuf/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Xavier Wang
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 2235 - 0
components/serialization/protobuf/luat_lib_protobuf.c

@@ -0,0 +1,2235 @@
+/*
+@module  protobuf
+@summary ProtoBuffs编解码
+@version 1.0
+@date    2022.09.08
+@demo protobuf
+@usage
+
+-- 加载 pb 文件, 这个是从pbtxt 转换得到的
+-- 转换命令: protoc.exe -operson.pb --cpp_out=cpp person.pbtxt
+-- protoc.exe 下载地址: https://github.com/protocolbuffers/protobuf/releases
+protobuf.load(io.readFile("/luadb/person.pb"))
+local tb = {
+    name = "wendal",
+    id = 123,
+    email = "abc@qq.com"
+}
+-- 用 protobuf 编码数据
+local data = protobuf.encode("Person", tb)
+if data then
+    -- 打印数据长度. 编码后的数据含不可见字符, toHex是方便显示
+    log.info("protobuf", #data, (data:toHex()))
+end
+*/
+#ifdef _MSC_VER
+# define _CRT_SECURE_NO_WARNINGS
+# define _CRT_NONSTDC_NO_WARNINGS
+# pragma warning(disable: 4244) /* int -> char */
+# pragma warning(disable: 4706) /* = in if condition */
+# pragma warning(disable: 4709) /* comma in array index */
+# pragma warning(disable: 4127) /* const in if condition */
+#endif
+
+#define PB_STATIC_API
+#include "pb.h"
+
+PB_NS_BEGIN
+
+
+#define LUA_LIB
+#include <lua.h>
+#include <lauxlib.h>
+
+
+#include <stdio.h>
+#include <errno.h>
+
+
+/* Lua util routines */
+
+#define PB_STATE     "pb.State"
+#define PB_BUFFER    "pb.Buffer"
+#define PB_SLICE     "pb.Slice"
+
+#define check_buffer(L,idx) ((pb_Buffer*)luaL_checkudata(L,idx,PB_BUFFER))
+#define test_buffer(L,idx)  ((pb_Buffer*)luaL_testudata(L,idx,PB_BUFFER))
+#define check_slice(L,idx)  ((pb_Slice*)luaL_checkudata(L,idx,PB_SLICE))
+#define test_slice(L,idx)   ((pb_Slice*)luaL_testudata(L,idx,PB_SLICE))
+#define push_slice(L,s)     lua_pushlstring((L), (s).p, pb_len((s)))
+#define return_self(L) { return lua_settop(L, 1), 1; }
+
+static int lpb_relindex(int idx, int offset) {
+    return idx < 0 && idx > LUA_REGISTRYINDEX ? idx - offset : idx;
+}
+
+#if LUA_VERSION_NUM < 502
+#include <assert.h>
+
+# define LUA_OK        0
+# define lua_rawlen    lua_objlen
+# define luaL_setfuncs(L,l,n) (assert(n==0), luaL_register(L,NULL,l))
+# define luaL_setmetatable(L, name) \
+    (luaL_getmetatable((L), (name)), lua_setmetatable(L, -2))
+
+static void lua_rawgetp(lua_State *L, int idx, const void *p) {
+    lua_pushlightuserdata(L, (void*)p);
+    lua_rawget(L, lpb_relindex(idx, 1));
+}
+
+static void lua_rawsetp(lua_State *L, int idx, const void *p) {
+    lua_pushlightuserdata(L, (void*)p);
+    lua_insert(L, -2);
+    lua_rawset(L, lpb_relindex(idx, 1));
+}
+
+#ifndef luaL_newlib /* not LuaJIT 2.1 */
+#define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l))
+
+static lua_Integer lua_tointegerx(lua_State *L, int idx, int *isint) {
+    lua_Integer i = lua_tointeger(L, idx);
+    if (isint) *isint = (i != 0 || lua_type(L, idx) == LUA_TNUMBER);
+    return i;
+}
+
+static lua_Number lua_tonumberx(lua_State *L, int idx, int *isnum) {
+    lua_Number i = lua_tonumber(L, idx);
+    if (isnum) *isnum = (i != 0 || lua_type(L, idx) == LUA_TNUMBER);
+    return i;
+}
+
+static void *luaL_testudata(lua_State *L, int idx, const char *type) {
+    void *p = lua_touserdata(L, idx);
+    if (p != NULL && lua_getmetatable(L, idx)) {
+        lua_getfield(L, LUA_REGISTRYINDEX, type);
+        if (!lua_rawequal(L, -2, -1))
+            p = NULL;
+        lua_pop(L, 2);
+        return p;
+    }
+    return NULL;
+}
+
+#endif
+
+#ifdef LUAI_BITSINT /* not LuaJIT */
+#include <errno.h>
+
+static int luaL_fileresult(lua_State *L, int stat, const char *fname) {
+    int en = errno;
+    if (stat) return lua_pushboolean(L, 1), 1;
+    lua_pushnil(L);
+    lua_pushfstring(L, "%s: %s", fname, strerror(en));
+    /*if (fname) lua_pushfstring(L, "%s: %s", fname, strerror(en));
+      else       lua_pushstring(L, strerror(en));*//* NOT USED */
+    lua_pushinteger(L, en);
+    return 3;
+}
+
+#endif /* not LuaJIT */
+
+#endif
+
+#if LUA_VERSION_NUM >= 503
+# define lua53_getfield lua_getfield
+# define lua53_rawgeti  lua_rawgeti
+# define lua53_rawgetp  lua_rawgetp
+#else /* not Lua 5.3 */
+static int lua53_getfield(lua_State *L, int idx, const char *field)
+{ return lua_getfield(L, idx, field), lua_type(L, -1); }
+static int lua53_rawgeti(lua_State *L, int idx, lua_Integer i)
+{ return lua_rawgeti(L, idx, i), lua_type(L, -1); }
+static int lua53_rawgetp(lua_State *L, int idx, const void *p)
+{ return lua_rawgetp(L, idx, p), lua_type(L, -1); }
+#endif
+
+
+/* protobuf global state */
+
+#define lpbS_state(LS)    ((LS)->state)
+#define lpb_name(LS,s)   pb_name(lpbS_state(LS), (s), &(LS)->cache)
+
+static const pb_State *global_state = NULL;
+static const char state_name[] = PB_STATE;
+
+enum lpb_Int64Mode { LPB_NUMBER, LPB_STRING, LPB_HEXSTRING };
+enum lpb_EncodeMode   { LPB_DEFDEF, LPB_COPYDEF, LPB_METADEF, LPB_NODEF };
+
+typedef struct lpb_State {
+    const pb_State *state;
+    pb_State  local;
+    pb_Cache  cache;
+    pb_Buffer buffer;
+    int defs_index;
+    int enc_hooks_index;
+    int dec_hooks_index;
+    unsigned use_dec_hooks : 1;
+    unsigned use_enc_hooks : 1;
+    unsigned enum_as_value : 1;
+    unsigned encode_mode   : 2; /* lpb_EncodeMode */
+    unsigned int64_mode    : 2; /* lpb_Int64Mode */
+    unsigned encode_default_values  : 1;
+    unsigned decode_default_array   : 1;
+    unsigned decode_default_message : 1;
+    unsigned encode_order  : 1;
+} lpb_State;
+
+static int lpb_reftable(lua_State *L, int ref) {
+    if (ref != LUA_NOREF) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
+        return ref;
+    } else {
+        lua_newtable(L);
+        lua_pushvalue(L, -1);
+        return luaL_ref(L, LUA_REGISTRYINDEX);
+    }
+}
+
+static void lpb_pushdeftable(lua_State *L, lpb_State *LS)
+{ LS->defs_index = lpb_reftable(L, LS->defs_index); }
+
+static void lpb_pushenchooktable(lua_State *L, lpb_State *LS)
+{ LS->enc_hooks_index = lpb_reftable(L, LS->enc_hooks_index); }
+
+static void lpb_pushdechooktable(lua_State *L, lpb_State *LS)
+{ LS->dec_hooks_index = lpb_reftable(L, LS->dec_hooks_index); }
+
+static int Lpb_delete(lua_State *L) {
+    lpb_State *LS = (lpb_State*)luaL_testudata(L, 1, PB_STATE);
+    if (LS != NULL) {
+        const pb_State *GS = global_state;
+        pb_free(&LS->local);
+        if (&LS->local == GS)
+            global_state = NULL;
+        LS->state = NULL;
+        pb_resetbuffer(&LS->buffer);
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->defs_index);
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->enc_hooks_index);
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->dec_hooks_index);
+    }
+    return 0;
+}
+
+LUALIB_API lpb_State *lpb_lstate(lua_State *L) {
+    lpb_State *LS;
+    if (lua53_rawgetp(L, LUA_REGISTRYINDEX, state_name) == LUA_TUSERDATA) {
+        LS = (lpb_State*)lua_touserdata(L, -1);
+        lua_pop(L, 1);
+    } else {
+        lua_pop(L, 1);
+        LS = (lpb_State*)lua_newuserdata(L, sizeof(lpb_State));
+        memset(LS, 0, sizeof(lpb_State));
+        LS->defs_index = LUA_NOREF;
+        LS->enc_hooks_index = LUA_NOREF;
+        LS->dec_hooks_index = LUA_NOREF;
+        LS->state = &LS->local;
+        pb_init(&LS->local);
+        pb_initbuffer(&LS->buffer);
+        luaL_setmetatable(L, PB_STATE);
+        lua_rawsetp(L, LUA_REGISTRYINDEX, state_name);
+    }
+    return LS;
+}
+
+static int Lpb_state(lua_State *L) {
+    int top = lua_gettop(L);
+    lpb_lstate(L);
+    lua_rawgetp(L, LUA_REGISTRYINDEX, state_name);
+    if (top != 0) {
+        if (lua_isnil(L, 1))
+            lua_pushnil(L);
+        else {
+            luaL_checkudata(L, 1, PB_STATE);
+            lua_pushvalue(L, 1);
+        }
+        lua_rawsetp(L, LUA_REGISTRYINDEX, state_name);
+    }
+    return 1;
+}
+
+
+/* protobuf util routines */
+
+static void lpb_addlength(lua_State *L, pb_Buffer *b, size_t len)
+{ if (pb_addlength(b, len) == 0) luaL_error(L, "encode bytes fail"); }
+
+static int typeerror(lua_State *L, int idx, const char *type) {
+    lua_pushfstring(L, "%s expected, got %s", type, luaL_typename(L, idx));
+    return luaL_argerror(L, idx, lua_tostring(L, -1));
+}
+
+static lua_Integer posrelat(lua_Integer pos, size_t len) {
+    if (pos >= 0) return pos;
+    else if (0u - (size_t)pos > len) return 0;
+    else return (lua_Integer)len + pos + 1;
+}
+
+static lua_Integer rangerelat(lua_State *L, int idx, lua_Integer r[2], size_t len) {
+    r[0] = posrelat(luaL_optinteger(L, idx, 1), len);
+    r[1] = posrelat(luaL_optinteger(L, idx+1, len), len);
+    if (r[0] < 1) r[0] = 1;
+    if (r[1] > (lua_Integer)len) r[1] = len;
+    return r[0] <= r[1] ? r[1] - r[0] + 1 : 0;
+}
+
+static int argcheck(lua_State *L, int cond, int idx, const char *fmt, ...) {
+    if (!cond) {
+        va_list l;
+        va_start(l, fmt);
+        lua_pushvfstring(L, fmt, l);
+        va_end(l);
+        return luaL_argerror(L, idx, lua_tostring(L, -1));
+    }
+    return 1;
+}
+
+static pb_Slice lpb_toslice(lua_State *L, int idx) {
+    int type = lua_type(L, idx);
+    if (type == LUA_TSTRING) {
+        size_t len;
+        const char *s = lua_tolstring(L, idx, &len);
+        return pb_lslice(s, len);
+    } else if (type == LUA_TUSERDATA) {
+        pb_Buffer *buffer;
+        pb_Slice *s;
+        if ((buffer = test_buffer(L, idx)) != NULL)
+            return pb_result(buffer);
+        else if ((s = test_slice(L, idx)) != NULL)
+            return *s;
+    }
+    return pb_slice(NULL);
+}
+
+LUALIB_API pb_Slice lpb_checkslice(lua_State *L, int idx) {
+    pb_Slice ret = lpb_toslice(L, idx);
+    if (ret.p == NULL) typeerror(L, idx, "string/buffer/slice");
+    return ret;
+}
+
+static void lpb_readbytes(lua_State *L, pb_Slice *s, pb_Slice *pv) {
+    uint64_t len = 0;
+    if (pb_readvarint64(s, &len) == 0 || len > PB_MAX_SIZET)
+        luaL_error(L, "invalid bytes length: %d (at offset %d)",
+                (int)len, pb_pos(*s)+1);
+    if (pb_readslice(s, (size_t)len, pv) == 0 && len != 0)
+        luaL_error(L, "unfinished bytes (len %d at offset %d)",
+                (int)len, pb_pos(*s)+1);
+}
+
+static int lpb_hexchar(char ch) {
+    switch (ch) {
+    case '0': return 0;
+    case '1': return 1; case '2': return 2; case '3': return 3;
+    case '4': return 4; case '5': return 5; case '6': return 6;
+    case '7': return 7; case '8': return 8; case '9': return 9;
+    case 'a': case 'A': return 10; case 'b': case 'B': return 11;
+    case 'c': case 'C': return 12; case 'd': case 'D': return 13;
+    case 'e': case 'E': return 14; case 'f': case 'F': return 15;
+    }
+    return -1;
+}
+
+static uint64_t lpb_tointegerx(lua_State *L, int idx, int *isint) {
+    int neg = 0;
+    const char *s, *os;
+#if LUA_VERSION_NUM >= 503
+    uint64_t v = (uint64_t)lua_tointegerx(L, idx, isint);
+    if (*isint) return v;
+#else
+    uint64_t v = 0;
+    lua_Number nv = lua_tonumberx(L, idx, isint);
+    if (*isint) {
+        if (nv < (lua_Number)INT64_MIN || nv > (lua_Number)INT64_MAX)
+            luaL_error(L, "number has no integer representation");
+        return (uint64_t)(int64_t)nv;
+    }
+#endif
+    if ((os = s = lua_tostring(L, idx)) == NULL) return 0;
+    while (*s == '#' || *s == '+' || *s == '-')
+        neg = (*s == '-') ^ neg, ++s;
+    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
+        for (s += 2; *s != '\0'; ++s) {
+            int n = lpb_hexchar(*s);
+            if (n < 0) break;
+            v = v << 4 | n;
+        }
+    } else {
+        for (; *s != '\0'; ++s) {
+            int n = lpb_hexchar(*s);
+            if (n < 0 || n > 10) break;
+            v = v * 10 + n;
+        }
+    }
+    if (*s != '\0') luaL_error(L, "integer format error: '%s'", os);
+    *isint = 1;
+    return neg ? ~v + 1 : v;
+}
+
+static uint64_t lpb_checkinteger(lua_State *L, int idx) {
+    int isint;
+    uint64_t v = lpb_tointegerx(L, idx, &isint);
+    if (!isint) typeerror(L, idx, "number/string");
+    return v;
+}
+
+static void lpb_pushinteger(lua_State *L, int64_t n, int mode) {
+    if (mode != LPB_NUMBER && (n < INT_MIN || n > UINT_MAX)) {
+        char buff[32], *p = buff + sizeof(buff) - 1;
+        int neg = n < 0;
+        uint64_t un = neg ? ~(uint64_t)n + 1 : (uint64_t)n;
+        if (mode == LPB_STRING) {
+            for (*p = '\0'; un > 0; un /= 10)
+                *--p = "0123456789"[un % 10];
+        } else if (mode == LPB_HEXSTRING) {
+            for (*p = '\0'; un > 0; un >>= 4)
+                *--p = "0123456789ABCDEF"[un & 0xF];
+            *--p = 'x', *--p = '0';
+        }
+        if (neg) *--p = '-';
+        *--p = '#';
+        lua_pushstring(L, p);
+    } else if (LUA_VERSION_NUM >= 503 && (n >> 32) == 0) // 原版是判断lua_Integer是否8字节
+        lua_pushinteger(L, (lua_Integer)n);
+    else
+        lua_pushnumber(L, (lua_Number)n);
+}
+
+typedef union lpb_Value {
+    pb_Slice    s[1];
+    uint32_t    u32;
+    uint64_t    u64;
+    lua_Integer lint;
+    lua_Number  lnum;
+} lpb_Value;
+
+static int lpb_addtype(lua_State *L, pb_Buffer *b, int idx, int type, size_t *plen) {
+    int ret = 0, expected = LUA_TNUMBER;
+    lpb_Value v;
+    size_t len = 0;
+    switch (type) {
+    case PB_Tbool:
+        len = pb_addvarint32(b, ret = lua_toboolean(L, idx));
+        if (ret) len = 0;
+        ret = 1;
+        break;
+    case PB_Tdouble:
+        v.lnum = lua_tonumberx(L, idx, &ret);
+        if (ret) len = pb_addfixed64(b, pb_encode_double((double)v.lnum));
+        if (v.lnum != 0.0) len = 0;
+        break;
+    case PB_Tfloat:
+        v.lnum = lua_tonumberx(L, idx, &ret);
+        if (ret) len = pb_addfixed32(b, pb_encode_float((float)v.lnum));
+        if (v.lnum != 0.0) len = 0;
+        break;
+    case PB_Tfixed32:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addfixed32(b, v.u32);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tsfixed32:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addfixed32(b, v.u32);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tint32:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addvarint64(b, pb_expandsig((uint32_t)v.u64));
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tuint32:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addvarint32(b, v.u32);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tsint32:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addvarint32(b, pb_encode_sint32(v.u32));
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tfixed64:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addfixed64(b, v.u64);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tsfixed64:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addfixed64(b, v.u64);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tint64: case PB_Tuint64:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addvarint64(b, v.u64);
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tsint64:
+        v.u64 = lpb_tointegerx(L, idx, &ret);
+        if (ret) len = pb_addvarint64(b, pb_encode_sint64(v.u64));
+        if (v.u64 != 0) len = 0;
+        break;
+    case PB_Tbytes: case PB_Tstring:
+        *v.s = lpb_toslice(L, idx);
+        if ((ret = (v.s->p != NULL))) len = pb_addbytes(b, *v.s);
+        if (pb_len(*v.s) != 0) len = 0;
+        expected = LUA_TSTRING;
+        break;
+    default:
+        lua_pushfstring(L, "unknown type %s", pb_typename(type, "<unknown>"));
+        if (idx > 0) argcheck(L, 0, idx, lua_tostring(L, -1));
+        lua_error(L);
+    }
+    if (plen) *plen = len;
+    return ret ? 0 : expected;
+}
+
+static void lpb_readtype(lua_State *L, lpb_State *LS, int type, pb_Slice *s) {
+    lpb_Value v;
+    switch (type) {
+#define pushinteger(n) lpb_pushinteger((L), (n), LS->int64_mode)
+    case PB_Tbool:  case PB_Tenum:
+    case PB_Tint32: case PB_Tuint32: case PB_Tsint32:
+    case PB_Tint64: case PB_Tuint64: case PB_Tsint64:
+        if (pb_readvarint64(s, &v.u64) == 0)
+            luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1);
+        switch (type) {
+        case PB_Tbool:   lua_pushboolean(L, v.u64 != 0); break;
+         /*case PB_Tenum:   pushinteger(v.u64); break; [> NOT REACHED <]*/
+        case PB_Tint32:  pushinteger((int32_t)v.u64); break;
+        case PB_Tuint32: pushinteger((uint32_t)v.u64); break;
+        case PB_Tsint32: pushinteger(pb_decode_sint32((uint32_t)v.u64)); break;
+        case PB_Tint64:  pushinteger((int64_t)v.u64); break;
+        case PB_Tuint64: pushinteger((uint64_t)v.u64); break;
+        case PB_Tsint64: pushinteger(pb_decode_sint64(v.u64)); break;
+        }
+        break;
+    case PB_Tfloat:
+    case PB_Tfixed32:
+    case PB_Tsfixed32:
+        if (pb_readfixed32(s, &v.u32) == 0)
+            luaL_error(L, "invalid fixed32 value at offset %d", pb_pos(*s)+1);
+        switch (type) {
+        case PB_Tfloat:    lua_pushnumber(L, pb_decode_float(v.u32)); break;
+        case PB_Tfixed32:  pushinteger(v.u32); break;
+        case PB_Tsfixed32: pushinteger((int32_t)v.u32); break;
+        }
+        break;
+    case PB_Tdouble:
+    case PB_Tfixed64:
+    case PB_Tsfixed64:
+        if (pb_readfixed64(s, &v.u64) == 0)
+            luaL_error(L, "invalid fixed64 value at offset %d", pb_pos(*s)+1);
+        switch (type) {
+        case PB_Tdouble:   lua_pushnumber(L, pb_decode_double(v.u64)); break;
+        case PB_Tfixed64:  pushinteger(v.u64); break;
+        case PB_Tsfixed64: pushinteger((int64_t)v.u64); break;
+        }
+        break;
+    case PB_Tbytes:
+    case PB_Tstring:
+    case PB_Tmessage:
+        lpb_readbytes(L, s, v.s);
+        push_slice(L, *v.s);
+        break;
+    default:
+        luaL_error(L, "unknown type %s (%d)", pb_typename(type, NULL), type);
+    }
+}
+
+
+/* io routines */
+
+#ifdef _WIN32
+# include <io.h>
+# include <fcntl.h>
+#else
+# define setmode(a,b)  ((void)0)
+#endif
+
+static int io_read(lua_State *L) {
+    FILE *fp = (FILE*)lua_touserdata(L, 1);
+    size_t nr;
+    luaL_Buffer b;
+    luaL_buffinit(L, &b);
+    do {  /* read file in chunks of LUAL_BUFFERSIZE bytes */
+        char *p = luaL_prepbuffer(&b);
+        nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, fp);
+        luaL_addsize(&b, nr);
+    } while (nr == LUAL_BUFFERSIZE);
+    luaL_pushresult(&b);  /* close buffer */
+    return 1;
+}
+
+static int io_write(lua_State *L, FILE *f, int idx) {
+    int nargs = lua_gettop(L) - idx + 1;
+    int status = 1;
+    for (; nargs--; idx++) {
+        pb_Slice s = lpb_checkslice(L, idx);
+        size_t l = pb_len(s);
+        status = status && (fwrite(s.p, sizeof(char), l, f) == l);
+    }
+    return status ? 1 : luaL_fileresult(L, 0, NULL);
+}
+
+static int Lio_read(lua_State *L) {
+    const char *fname = luaL_optstring(L, 1, NULL);
+    FILE *fp = stdin;
+    int ret;
+    if (fname == NULL)
+        (void)setmode(fileno(stdin), O_BINARY);
+    else if ((fp = fopen(fname, "rb")) == NULL)
+        return luaL_fileresult(L, 0, fname);
+    lua_pushcfunction(L, io_read);
+    lua_pushlightuserdata(L, fp);
+    ret = lua_pcall(L, 1, 1, 0);
+    if (fp != stdin) fclose(fp);
+    else (void)setmode(fileno(stdin), O_TEXT);
+    if (ret != LUA_OK) { lua_pushnil(L); lua_insert(L, -2); return 2; }
+    return 1;
+}
+
+static int Lio_write(lua_State *L) {
+    int res;
+    (void)setmode(fileno(stdout), O_BINARY);
+    res = io_write(L, stdout, 1);
+    fflush(stdout);
+    (void)setmode(fileno(stdout), O_TEXT);
+    return res;
+}
+
+static int Lio_dump(lua_State *L) {
+    int res;
+    const char *fname = luaL_checkstring(L, 1);
+    FILE *fp = fopen(fname, "wb");
+    if (fp == NULL) return luaL_fileresult(L, 0, fname);
+    res = io_write(L, fp, 2);
+    fclose(fp);
+    return res;
+}
+
+LUALIB_API int luaopen_pb_io(lua_State *L) {
+    luaL_Reg libs[] = {
+#define ENTRY(name) { #name, Lio_##name }
+        ENTRY(read),
+        ENTRY(write),
+        ENTRY(dump),
+#undef  ENTRY
+        { NULL, NULL }
+    };
+    luaL_newlib(L, libs);
+    return 1;
+}
+
+
+/* protobuf integer conversion */
+
+static int Lconv_encode_int32(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    uint64_t v = pb_expandsig((int32_t)lpb_checkinteger(L, 1));
+    lpb_pushinteger(L, v, mode);
+    return 1;
+}
+
+static int Lconv_encode_uint32(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, (uint32_t)lpb_checkinteger(L, 1), mode);
+    return 1;
+}
+
+static int Lconv_encode_sint32(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_encode_sint32((int32_t)lpb_checkinteger(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_decode_sint32(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_decode_sint32((uint32_t)lpb_checkinteger(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_encode_sint64(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_encode_sint64(lpb_checkinteger(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_decode_sint64(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_decode_sint64(lpb_checkinteger(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_encode_float(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_encode_float((float)luaL_checknumber(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_decode_float(lua_State *L) {
+    lua_pushnumber(L, pb_decode_float((uint32_t)lpb_checkinteger(L, 1)));
+    return 1;
+}
+
+static int Lconv_encode_double(lua_State *L) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_pushinteger(L, pb_encode_double(luaL_checknumber(L, 1)), mode);
+    return 1;
+}
+
+static int Lconv_decode_double(lua_State *L) {
+    lua_pushnumber(L, pb_decode_double(lpb_checkinteger(L, 1)));
+    return 1;
+}
+
+LUALIB_API int luaopen_pb_conv(lua_State *L) {
+    luaL_Reg libs[] = {
+        { "decode_uint32", Lconv_encode_uint32 },
+        { "decode_int32", Lconv_encode_int32 },
+#define ENTRY(name) { #name, Lconv_##name }
+        ENTRY(encode_int32),
+        ENTRY(encode_uint32),
+        ENTRY(encode_sint32),
+        ENTRY(encode_sint64),
+        ENTRY(decode_sint32),
+        ENTRY(decode_sint64),
+        ENTRY(decode_float),
+        ENTRY(decode_double),
+        ENTRY(encode_float),
+        ENTRY(encode_double),
+#undef  ENTRY
+        { NULL, NULL }
+    };
+    luaL_newlib(L, libs);
+    return 1;
+}
+
+
+/* protobuf encode routine */
+
+static int lpb_typefmt(int fmt) {
+    switch (fmt) {
+#define X(name,type,fmt) case fmt: return PB_T##name;
+        PB_TYPES(X)
+#undef  X
+    }
+    return -1;
+}
+
+static int lpb_packfmt(lua_State *L, int idx, pb_Buffer *b, const char **pfmt, int level) {
+    const char *fmt = *pfmt;
+    int type, ltype;
+    size_t len;
+    argcheck(L, level <= 100, 1, "format level overflow");
+    for (; *fmt != '\0'; ++fmt) {
+        switch (*fmt) {
+        case 'v': pb_addvarint64(b, (uint64_t)lpb_checkinteger(L, idx++)); break;
+        case 'd': pb_addfixed32(b, (uint32_t)lpb_checkinteger(L, idx++)); break;
+        case 'q': pb_addfixed64(b, (uint64_t)lpb_checkinteger(L, idx++)); break;
+        case 'c': pb_addslice(b, lpb_checkslice(L, idx++)); break;
+        case 's': pb_addbytes(b, lpb_checkslice(L, idx++)); break;
+        case '#': lpb_addlength(L, b, (size_t)lpb_checkinteger(L, idx++)); break;
+        case '(':
+            len = pb_bufflen(b);
+            ++fmt;
+            idx = lpb_packfmt(L, idx, b, &fmt, level+1);
+            lpb_addlength(L, b, len);
+            break;
+        case ')':
+            if (level == 0) luaL_argerror(L, 1, "unexpected ')' in format");
+            *pfmt = fmt;
+            return idx;
+        case '\0':
+        default:
+            argcheck(L, (type = lpb_typefmt(*fmt)) >= 0,
+                    1, "invalid formater: '%c'", *fmt);
+            ltype = lpb_addtype(L, b, idx, type, NULL);
+            argcheck(L, ltype == 0, idx, "%s expected for type '%s', got %s",
+                    lua_typename(L, ltype), pb_typename(type, "<unknown>"),
+                    luaL_typename(L, idx));
+            ++idx;
+        }
+    }
+    if (level != 0) luaL_argerror(L, 2, "unmatch '(' in format");
+    *pfmt = fmt;
+    return idx;
+}
+
+static int Lpb_tohex(lua_State *L) {
+    pb_Slice s = lpb_checkslice(L, 1);
+    const char *hexa = "0123456789ABCDEF";
+    char hex[4] = "XX ";
+    lua_Integer r[2] = { 1, -1 };
+    luaL_Buffer lb;
+    rangerelat(L, 2, r, pb_len(s));
+    luaL_buffinit(L, &lb);
+    for (; r[0] <= r[1]; ++r[0]) {
+        unsigned int ch = s.p[r[0]-1];
+        hex[0] = hexa[(ch>>4)&0xF];
+        hex[1] = hexa[(ch   )&0xF];
+        if (r[0] == r[1]) hex[2] = '\0';
+        luaL_addstring(&lb, hex);
+    }
+    luaL_pushresult(&lb);
+    return 1;
+}
+
+static int Lpb_fromhex(lua_State *L) {
+    pb_Slice s = lpb_checkslice(L, 1);
+    lua_Integer r[2] = { 1, -1 };
+    luaL_Buffer lb;
+    int curr = 0, idx = 0, num;
+    rangerelat(L, 2, r, pb_len(s));
+    luaL_buffinit(L, &lb);
+    for (; r[0] <= r[1]; ++r[0]) {
+        switch (num = s.p[r[0]-1]) {
+        case '0': case '1': case '2': case '3':
+        case '4': case '5': case '6': case '7':
+        case '8': case '9': num -= '0'; break;
+        case 'A': case 'a': num  =  10; break;
+        case 'B': case 'b': num  =  11; break;
+        case 'C': case 'c': num  =  12; break;
+        case 'D': case 'd': num  =  13; break;
+        case 'E': case 'e': num  =  14; break;
+        case 'F': case 'f': num  =  15; break;
+        default: continue;
+        }
+        curr = curr<<4 | num;
+        if (++idx % 2 == 0) luaL_addchar(&lb, curr), curr = 0;
+    }
+    luaL_pushresult(&lb);
+    return 1;
+}
+
+static int Lpb_result(lua_State *L) {
+    pb_Slice s = lpb_checkslice(L, 1);
+    lua_Integer r[2] = {1, -1}, range = rangerelat(L, 2, r, pb_len(s));
+    lua_pushlstring(L, s.p+r[0]-1, (size_t)range);
+    return 1;
+}
+
+static int Lbuf_new(lua_State *L) {
+    int i, top = lua_gettop(L);
+    pb_Buffer *buf = (pb_Buffer*)lua_newuserdata(L, sizeof(pb_Buffer));
+    pb_initbuffer(buf);
+    luaL_setmetatable(L, PB_BUFFER);
+    for (i = 1; i <= top; ++i)
+        pb_addslice(buf, lpb_checkslice(L, i));
+    return 1;
+}
+
+static int Lbuf_delete(lua_State *L) {
+    pb_Buffer *buf = test_buffer(L, 1);
+    if (buf) pb_resetbuffer(buf);
+    return 0;
+}
+
+static int Lbuf_libcall(lua_State *L) {
+    int i, top = lua_gettop(L);
+    pb_Buffer *buf = (pb_Buffer*)lua_newuserdata(L, sizeof(pb_Buffer));
+    pb_initbuffer(buf);
+    luaL_setmetatable(L, PB_BUFFER);
+    for (i = 2; i <= top; ++i)
+        pb_addslice(buf, lpb_checkslice(L, i));
+    return 1;
+}
+
+static int Lbuf_tostring(lua_State *L) {
+    pb_Buffer *buf = check_buffer(L, 1);
+    lua_pushfstring(L, "pb.Buffer: %p", buf);
+    return 1;
+}
+
+static int Lbuf_reset(lua_State *L) {
+    pb_Buffer *buf = check_buffer(L, 1);
+    int i, top = lua_gettop(L);
+    pb_bufflen(buf) = 0;
+    for (i = 2; i <= top; ++i)
+        pb_addslice(buf, lpb_checkslice(L, i));
+    return_self(L);
+}
+
+static int Lbuf_len(lua_State *L) {
+    pb_Buffer *buf = check_buffer(L, 1);
+    lua_pushinteger(L, (lua_Integer)buf->size);
+    return 1;
+}
+
+static int Lbuf_pack(lua_State *L) {
+    pb_Buffer b, *pb = test_buffer(L, 1);
+    int idx = 1 + (pb != NULL);
+    const char *fmt = luaL_checkstring(L, idx++);
+    if (pb == NULL) pb_initbuffer(pb = &b);
+    lpb_packfmt(L, idx, pb, &fmt, 0);
+    if (pb != &b)
+        lua_settop(L, 1);
+    else {
+        pb_Slice ret = pb_result(pb);
+        push_slice(L, ret);
+        pb_resetbuffer(pb);
+    }
+    return 1;
+}
+
+LUALIB_API int luaopen_pb_buffer(lua_State *L) {
+    luaL_Reg libs[] = {
+        { "__tostring", Lbuf_tostring },
+        { "__len",      Lbuf_len },
+        { "__gc",       Lbuf_delete },
+        { "delete",     Lbuf_delete },
+        { "tohex",      Lpb_tohex },
+        { "fromhex",    Lpb_fromhex },
+        { "result",     Lpb_result },
+#define ENTRY(name) { #name, Lbuf_##name }
+        ENTRY(new),
+        ENTRY(reset),
+        ENTRY(pack),
+#undef  ENTRY
+        { NULL, NULL }
+    };
+    if (luaL_newmetatable(L, PB_BUFFER)) {
+        luaL_setfuncs(L, libs, 0);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -2, "__index");
+        lua_createtable(L, 0, 1);
+        lua_pushcfunction(L, Lbuf_libcall);
+        lua_setfield(L, -2, "__call");
+        lua_setmetatable(L, -2);
+    }
+    return 1;
+}
+
+
+/* protobuf decode routine */
+
+#define LPB_INITSTACKLEN 2
+
+typedef struct lpb_Slice {
+    pb_Slice  curr;
+    pb_Slice *buff;
+    size_t       used;
+    size_t       size;
+    pb_Slice  init_buff[LPB_INITSTACKLEN];
+} lpb_Slice;
+
+static void lpb_resetslice(lua_State *L, lpb_Slice *s, size_t size) {
+    if (size == sizeof(lpb_Slice)) {
+        if (s->buff != s->init_buff) free(s->buff);
+        memset(s, 0, sizeof(lpb_Slice));
+        s->buff = s->init_buff;
+        s->size = LPB_INITSTACKLEN;
+    }
+    lua_pushnil(L);
+    lua_rawsetp(L, LUA_REGISTRYINDEX, s);
+}
+
+static pb_Slice lpb_checkview(lua_State *L, int idx, pb_Slice *ps) {
+    pb_Slice src = lpb_checkslice(L, idx);
+    lua_Integer r[2] = {1, -1}, range = rangerelat(L, idx+1, r, pb_len(src));
+    pb_Slice ret;
+    if (ps) *ps = src, ps->start = src.p;
+    ret.p     = src.p + r[0] - 1;
+    ret.end   = ret.p + range;
+    ret.start = src.p;
+    return ret;
+}
+
+static void lpb_enterview(lua_State *L, lpb_Slice *s, pb_Slice view) {
+    if (s->used >= s->size) {
+        size_t newsize = s->size * 2;
+        pb_Slice *oldp = s->buff != s->init_buff ? s->buff : NULL;
+        pb_Slice *newp = (pb_Slice*)realloc(oldp, newsize*sizeof(pb_Slice));
+        if (newp == NULL) { luaL_error(L, "out of memory"); return; }
+        if (oldp == NULL) memcpy(newp, s->buff, s->used*sizeof(pb_Slice));
+        s->buff = newp;
+        s->size = newsize;
+    }
+    s->buff[s->used++] = s->curr;
+    s->curr = view;
+}
+
+static void lpb_initslice(lua_State *L, int idx, lpb_Slice *s, size_t size) {
+    if (size == sizeof(lpb_Slice)) {
+        memset(s, 0, sizeof(lpb_Slice));
+        s->buff = s->init_buff;
+        s->size = LPB_INITSTACKLEN;
+    }
+    if (!lua_isnoneornil(L, idx)) {
+        pb_Slice base, view = lpb_checkview(L, idx, &base);
+        s->curr = base;
+        if (size == sizeof(lpb_Slice)) lpb_enterview(L, s, view);
+        lua_pushvalue(L, idx);
+        lua_rawsetp(L, LUA_REGISTRYINDEX, s);
+    }
+}
+
+static int lpb_unpackscalar(lua_State *L, int *pidx, int top, int fmt, pb_Slice *s) {
+    unsigned mode = lpb_lstate(L)->int64_mode;
+    lpb_Value v;
+    switch (fmt) {
+    case 'v':
+        if (pb_readvarint64(s, &v.u64) == 0)
+            luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1);
+        lpb_pushinteger(L, v.u64, mode);
+        break;
+    case 'd':
+        if (pb_readfixed32(s, &v.u32) == 0)
+            luaL_error(L, "invalid fixed32 value at offset %d", pb_pos(*s)+1);
+        lpb_pushinteger(L, v.u32, mode);
+        break;
+    case 'q':
+        if (pb_readfixed64(s, &v.u64) == 0)
+            luaL_error(L, "invalid fixed64 value at offset %d", pb_pos(*s)+1);
+        lpb_pushinteger(L, v.u64, mode);
+        break;
+    case 's':
+        if (pb_readbytes(s, v.s) == 0)
+            luaL_error(L, "invalid bytes value at offset %d", pb_pos(*s)+1);
+        push_slice(L, *v.s);
+        break;
+    case 'c':
+        argcheck(L, *pidx <= top, 1, "format argument exceed");
+        v.lint = luaL_checkinteger(L, (*pidx)++);
+        if (pb_readslice(s, (size_t)v.lint, v.s) == 0)
+            luaL_error(L, "invalid sub string at offset %d", pb_pos(*s)+1);
+        push_slice(L, *v.s);
+        break;
+    default:
+        return 0;
+    }
+    return 1;
+}
+
+static int lpb_unpackloc(lua_State *L, int *pidx, int top, int fmt, pb_Slice *s, int *prets) {
+    lua_Integer li;
+    size_t len = s->end - s->start;
+    switch (fmt) {
+    case '@':
+        lua_pushinteger(L, pb_pos(*s)+1);
+        ++*prets;
+        break;
+
+    case '*': case '+':
+        argcheck(L, *pidx <= top, 1, "format argument exceed");
+        if (fmt == '*')
+            li = posrelat(luaL_checkinteger(L, (*pidx)++), len);
+        else
+            li = pb_pos(*s) + luaL_checkinteger(L, (*pidx)++) + 1;
+        if (li == 0) li = 1;
+        if (li > (lua_Integer)len) li = (lua_Integer)len + 1;
+        s->p = s->start + li - 1;
+        break;
+
+    default:
+        return 0;
+    }
+    return 1;
+}
+
+static int lpb_unpackfmt(lua_State *L, int idx, const char *fmt, pb_Slice *s) {
+    int rets = 0, top = lua_gettop(L), type;
+    for (; *fmt != '\0'; ++fmt) {
+        if (lpb_unpackloc(L, &idx, top, *fmt, s, &rets))
+            continue;
+        if (s->p >= s->end) return lua_pushnil(L), rets + 1;
+        luaL_checkstack(L, 1, "too many values");
+        if (!lpb_unpackscalar(L, &idx, top, *fmt, s)) {
+            argcheck(L, (type = lpb_typefmt(*fmt)) >= 0,
+                    1, "invalid formater: '%c'", *fmt);
+            lpb_readtype(L, lpb_lstate(L), type, s);
+        }
+        ++rets;
+    }
+    return rets;
+}
+
+static lpb_Slice *check_lslice(lua_State *L, int idx) {
+    pb_Slice *s = check_slice(L, idx);
+    argcheck(L, lua_rawlen(L, 1) == sizeof(lpb_Slice),
+            idx, "unsupport operation for raw mode slice");
+    return (lpb_Slice*)s;
+}
+
+static int Lslice_new(lua_State *L) {
+    lpb_Slice *s;
+    lua_settop(L, 3);
+    s = (lpb_Slice*)lua_newuserdata(L, sizeof(lpb_Slice));
+    lpb_initslice(L, 1, s, sizeof(lpb_Slice));
+    if (s->curr.p == NULL) s->curr = pb_lslice("", 0);
+    luaL_setmetatable(L, PB_SLICE);
+    return 1;
+}
+
+static int Lslice_libcall(lua_State *L) {
+    lpb_Slice *s;
+    lua_settop(L, 4);
+    s = (lpb_Slice*)lua_newuserdata(L, sizeof(lpb_Slice));
+    lpb_initslice(L, 2, s, sizeof(lpb_Slice));
+    luaL_setmetatable(L, PB_SLICE);
+    return 1;
+}
+
+static int Lslice_reset(lua_State *L) {
+    lpb_Slice *s = (lpb_Slice*)check_slice(L, 1);
+    size_t size = lua_rawlen(L, 1);
+    lpb_resetslice(L, s, size);
+    if (!lua_isnoneornil(L, 2))
+        lpb_initslice(L, 2, s, size);
+    return_self(L);
+}
+
+static int Lslice_tostring(lua_State *L) {
+    pb_Slice *s = check_slice(L, 1);
+    lua_pushfstring(L, "pb.Slice: %p%s", s,
+            lua_rawlen(L, 1) == sizeof(lpb_Slice) ? "" : " (raw)");
+    return 1;
+}
+
+static int Lslice_len(lua_State *L) {
+    pb_Slice *s = check_slice(L, 1);
+    lua_pushinteger(L, (lua_Integer)pb_len(*s));
+    lua_pushinteger(L, (lua_Integer)pb_pos(*s)+1);
+    return 2;
+}
+
+static int Lslice_unpack(lua_State *L) {
+    pb_Slice view, *s = test_slice(L, 1);
+    const char *fmt = luaL_checkstring(L, 2);
+    if (s == NULL) view = lpb_checkslice(L, 1), s = &view;
+    return lpb_unpackfmt(L, 3, fmt, s);
+}
+
+static int Lslice_level(lua_State *L) {
+    lpb_Slice *s = check_lslice(L, 1);
+    if (!lua_isnoneornil(L, 2)) {
+        pb_Slice *se;
+        lua_Integer level = posrelat(luaL_checkinteger(L, 2), s->used);
+        if (level > (lua_Integer)s->used)
+            return 0;
+        else if (level == (lua_Integer)s->used)
+            se = &s->curr;
+        else
+            se = &s->buff[level];
+        lua_pushinteger(L, (lua_Integer)(se->p     - s->buff[0].start) + 1);
+        lua_pushinteger(L, (lua_Integer)(se->start - s->buff[0].start) + 1);
+        lua_pushinteger(L, (lua_Integer)(se->end   - s->buff[0].start));
+        return 3;
+    }
+    lua_pushinteger(L, s->used);
+    return 1;
+}
+
+static int Lslice_enter(lua_State *L) {
+    lpb_Slice *s = check_lslice(L, 1);
+    pb_Slice view;
+    if (lua_isnoneornil(L, 2)) {
+        argcheck(L, pb_readbytes(&s->curr, &view) != 0,
+            1, "bytes wireformat expected at offset %d", pb_pos(s->curr)+1);
+        view.start = view.p;
+        lpb_enterview(L, s, view);
+    } else {
+        lua_Integer r[] = {1, -1};
+        lua_Integer range = rangerelat(L, 2, r, pb_len(s->curr));
+        view.p     = s->curr.start + r[0] - 1;
+        view.end   = view.p + range;
+        view.start = s->curr.p;
+        lpb_enterview(L, s, view);
+    }
+    return_self(L);
+}
+
+static int Lslice_leave(lua_State *L) {
+    lpb_Slice *s = check_lslice(L, 1);
+    lua_Integer count = posrelat(luaL_optinteger(L, 2, 1), s->used);
+    if (count > (lua_Integer)s->used)
+        argcheck(L, 0, 2, "level (%d) exceed max level %d",
+                (int)count, (int)s->used);
+    else if (count == (lua_Integer)s->used) {
+        s->curr = s->buff[0];
+        s->used = 1;
+    } else {
+        s->used -= (size_t)count;
+        s->curr = s->buff[s->used];
+    }
+    lua_settop(L, 1);
+    lua_pushinteger(L, s->used);
+    return 2;
+}
+
+LUALIB_API int lpb_newslice(lua_State *L, const char *s, size_t len) {
+    pb_Slice *ls = (pb_Slice*)lua_newuserdata(L, sizeof(pb_Slice));
+    *ls = pb_lslice(s, len);
+    luaL_setmetatable(L, PB_SLICE);
+    return 1;
+}
+
+LUALIB_API int luaopen_pb_slice(lua_State *L) {
+    luaL_Reg libs[] = {
+        { "__tostring", Lslice_tostring },
+        { "__len",      Lslice_len   },
+        { "__gc",       Lslice_reset },
+        { "delete",     Lslice_reset },
+        { "tohex",      Lpb_tohex   },
+        { "fromhex",    Lpb_fromhex   },
+        { "result",     Lpb_result  },
+#define ENTRY(name) { #name, Lslice_##name }
+        ENTRY(new),
+        ENTRY(reset),
+        ENTRY(level),
+        ENTRY(enter),
+        ENTRY(leave),
+        ENTRY(unpack),
+#undef  ENTRY
+        { NULL, NULL }
+    };
+    if (luaL_newmetatable(L, PB_SLICE)) {
+        luaL_setfuncs(L, libs, 0);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -2, "__index");
+        lua_createtable(L, 0, 1);
+        lua_pushcfunction(L, Lslice_libcall);
+        lua_setfield(L, -2, "__call");
+        lua_setmetatable(L, -2);
+    }
+    return 1;
+}
+
+
+/* high level typeinfo/encode/decode routines */
+
+typedef enum {USE_FIELD = 1, USE_REPEAT = 2, USE_MESSAGE = 4} lpb_DefFlags;
+
+static void lpb_pushtypetable(lua_State *L, lpb_State *LS, const pb_Type *t);
+
+static void lpb_newmsgtable(lua_State *L, const pb_Type *t)
+{ lua_createtable(L, 0, t->field_count - t->oneof_field + t->oneof_count*2); }
+
+LUALIB_API const pb_Type *lpb_type(lpb_State *LS, pb_Slice s) {
+    const pb_Type *t;
+    if (s.p == NULL || *s.p == '.')
+        t = pb_type(lpbS_state(LS), lpb_name(LS, s));
+    else {
+        pb_Buffer b;
+        pb_initbuffer(&b);
+        *pb_prepbuffsize(&b, 1) = '.';
+        pb_addsize(&b, 1);
+        pb_addslice(&b, s);
+        t = pb_type(lpbS_state(LS), pb_name(lpbS_state(LS),pb_result(&b),NULL));
+        pb_resetbuffer(&b);
+    }
+    return t;
+}
+
+static const pb_Field *lpb_field(lua_State *L, int idx, const pb_Type *t) {
+    lpb_State *LS = lpb_lstate(L);
+    int isint, number = (int)lua_tointegerx(L, idx, &isint);
+    if (isint) return pb_field(t, number);
+    return pb_fname(t, lpb_name(LS, lpb_checkslice(L, idx)));
+}
+
+/*
+加载pb二进制定义数据
+@api protobuf.load(pbdata)
+@string 通过protoc.exe程序转换得到的数据,通常从文件读取得到
+@return bool 成功与否
+@return int 读取了多长, 调试用
+@usage
+-- 注意, 同一个文件只需要加载一次, 除非调用过protobuf.clear()
+protobuf.load(io.readFile("/luadb/person.pb"))
+*/
+static int Lpb_load(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    pb_Slice s = lpb_checkslice(L, 1);
+    int r = pb_load(&LS->local, &s);
+    if (r == PB_OK) global_state = &LS->local;
+    lua_pushboolean(L, r == PB_OK);
+    lua_pushinteger(L, pb_pos(s)+1);
+    return 2;
+}
+
+static int Lpb_loadfile(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const char *filename = luaL_checkstring(L, 1);
+    size_t size;
+    pb_Buffer b;
+    pb_Slice s;
+    int ret;
+    FILE *fp = fopen(filename, "rb");
+    if (fp == NULL)
+        return luaL_fileresult(L, 0, filename);
+    pb_initbuffer(&b);
+    do {
+        char *d = pb_prepbuffsize(&b, BUFSIZ);
+        if (d == NULL) return fclose(fp), luaL_error(L, "out of memory");
+        size = fread(d, 1, BUFSIZ, fp);
+        pb_addsize(&b, size);
+    } while (size == BUFSIZ);
+    fclose(fp);
+    s = pb_result(&b);
+    ret = pb_load(&LS->local, &s);
+    if (ret == PB_OK) global_state = &LS->local;
+    pb_resetbuffer(&b);
+    lua_pushboolean(L, ret == PB_OK);
+    lua_pushinteger(L, pb_pos(s)+1);
+    return 2;
+}
+
+static int lpb_pushtype(lua_State *L, const pb_Type *t) {
+    if (t == NULL) return 0;
+    lua_pushstring(L, (const char*)t->name);
+    lua_pushstring(L, (const char*)t->basename);
+    lua_pushstring(L, t->is_map ? "map" : t->is_enum ? "enum" : "message");
+    return 3;
+}
+
+static int lpb_pushfield(lua_State *L, const pb_Type *t, const pb_Field *f) {
+    if (f == NULL) return 0;
+    lua_pushstring(L, (const char*)f->name);
+    lua_pushinteger(L, f->number);
+    lua_pushstring(L, f->type ?
+            (const char*)f->type->name :
+            pb_typename(f->type_id, "<unknown>"));
+    lua_pushstring(L, (const char*)f->default_value);
+    lua_pushstring(L, f->repeated ?
+            (f->packed ? "packed" : "repeated") :
+            "optional");
+    if (f->oneof_idx > 0) {
+        lua_pushstring(L, (const char*)pb_oneofname(t, f->oneof_idx));
+        lua_pushinteger(L, f->oneof_idx-1);
+        return 7;
+    }
+    return 5;
+}
+
+static int Lpb_typesiter(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_toslice(L, 2));
+    if ((t == NULL && !lua_isnoneornil(L, 2)))
+        return 0;
+    pb_nexttype(lpbS_state(LS), &t);
+    return lpb_pushtype(L, t);
+}
+
+static int Lpb_types(lua_State *L) {
+    lua_pushcfunction(L, Lpb_typesiter);
+    lua_pushnil(L);
+    lua_pushnil(L);
+    return 3;
+}
+
+static int Lpb_fieldsiter(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    const pb_Field *f = pb_fname(t, lpb_name(LS, lpb_toslice(L, 2)));
+    if ((f == NULL && !lua_isnoneornil(L, 2)) || !pb_nextfield(t, &f))
+        return 0;
+    return lpb_pushfield(L, t, f);
+}
+
+static int Lpb_fields(lua_State *L) {
+    lua_pushcfunction(L, Lpb_fieldsiter);
+    lua_pushvalue(L, 1);
+    lua_pushnil(L);
+    return 3;
+}
+
+static int Lpb_type(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    if (t == NULL || t->is_dead)
+        return 0;
+    return lpb_pushtype(L, t);
+}
+
+static int Lpb_field(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    return lpb_pushfield(L, t, lpb_field(L, 2, t));
+}
+
+static int Lpb_enum(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    const pb_Field *f = lpb_field(L, 2, t);
+    if (f == NULL) return 0;
+    if (lua_type(L, 2) == LUA_TNUMBER)
+        lua_pushstring(L, (const char*)f->name);
+    else
+        lpb_pushinteger(L, f->number, LS->int64_mode);
+    return 1;
+}
+
+static int lpb_pushdeffield(lua_State *L, lpb_State *LS, const pb_Field *f, int is_proto3) {
+    int ret = 0;
+    const pb_Type *type;
+    char *end;
+    if (f == NULL) return 0;
+    switch (f->type_id) {
+    case PB_Tenum:
+        if ((type = f ? f->type : NULL) == NULL) return 0;
+        if ((f = pb_fname(type, f->default_value)) != NULL)
+            ret = LS->enum_as_value ?
+                (lpb_pushinteger(L, f->number, LS->int64_mode), 1) :
+                (lua_pushstring(L, (const char*)f->name), 1);
+        else if (is_proto3)
+            ret = (f = pb_field(type, 0)) == NULL || LS->enum_as_value ?
+                (lua_pushinteger(L, 0), 1) :
+                (lua_pushstring(L, (const char*)f->name), 1);
+        break;
+    case PB_Tmessage:
+        ret = (lpb_pushtypetable(L, LS, f->type), 1);
+        break;
+    case PB_Tbytes: case PB_Tstring:
+        if (f->default_value)
+            ret = (lua_pushstring(L, (const char*)f->default_value), 1);
+        else if (is_proto3) ret = (lua_pushliteral(L, ""), 1);
+        break;
+    case PB_Tbool:
+        if (f->default_value) {
+            if (f->default_value == lpb_name(LS, pb_slice("true")))
+                ret = (lua_pushboolean(L, 1), 1);
+            else if (f->default_value == lpb_name(LS, pb_slice("false")))
+                ret = (lua_pushboolean(L, 0), 1);
+        } else if (is_proto3) ret = (lua_pushboolean(L, 0), 1);
+        break;
+    case PB_Tdouble: case PB_Tfloat:
+        if (f->default_value) {
+            lua_Number ln = (lua_Number)strtod((const char*)f->default_value, &end);
+            if ((const char*)f->default_value == end) return 0;
+            ret = (lua_pushnumber(L, ln), 1);
+        } else if (is_proto3) ret = (lua_pushnumber(L, 0.0), 1);
+        break;
+
+    default:
+        if (f->default_value) {
+            lua_Integer li = (lua_Integer)strtol((const char*)f->default_value, &end, 10);
+            if ((const char*)f->default_value == end) return 0;
+            ret = (lpb_pushinteger(L, li, LS->int64_mode), 1);
+        } else if (is_proto3) ret = (lua_pushinteger(L, 0), 1);
+    }
+    return ret;
+}
+
+static void lpb_setdeffields(lua_State *L, lpb_State *LS, const pb_Type *t, lpb_DefFlags flags) {
+    const pb_Field *f = NULL;
+    while (pb_nextfield(t, &f)) {
+        int has_field = f->repeated ?
+            (flags & USE_REPEAT) && (t->is_proto3 || LS->decode_default_array)
+            && (lua_newtable(L), 1) :
+            !f->oneof_idx && (f->type_id != PB_Tmessage ?
+                    (flags & USE_FIELD) :
+                    (flags & USE_MESSAGE) && LS->decode_default_message)
+            && lpb_pushdeffield(L, LS, f, t->is_proto3);
+        if (has_field) lua_setfield(L, -2, (const char*)f->name);
+    }
+}
+
+static void lpb_pushdefmeta(lua_State *L, lpb_State *LS, const pb_Type *t) {
+    lpb_pushdeftable(L, LS);
+    if (lua53_rawgetp(L, -1, t) != LUA_TTABLE) {
+        lua_pop(L, 1);
+        lpb_newmsgtable(L, t);
+        lpb_setdeffields(L, LS, t, USE_FIELD);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -2, "__index");
+        lua_pushvalue(L, -1);
+        lua_rawsetp(L, -3, t);
+    }
+    lua_remove(L, -2);
+}
+
+static void lpb_cleardefmeta(lua_State *L, lpb_State *LS, const pb_Type *t) {
+    lpb_pushdeftable(L, LS);
+    lua_pushnil(L);
+    lua_rawsetp(L, -2, t);
+    lua_pop(L, 1);
+}
+
+static int Lpb_defaults(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    int clear = lua_toboolean(L, 2);
+    if (t == NULL) luaL_argerror(L, 1, "type not found");
+    lpb_pushdefmeta(L, LS, t);
+    if (clear) lpb_cleardefmeta(L, LS, t);
+    return 1;
+}
+
+static int Lpb_hook(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    int type = lua_type(L, 2);
+    if (t == NULL) luaL_argerror(L, 1, "type not found");
+    if (type != LUA_TNONE && type != LUA_TNIL && type != LUA_TFUNCTION)
+        typeerror(L, 2, "function");
+    lua_settop(L, 2);
+    lpb_pushdechooktable(L, LS);
+    lua_rawgetp(L, 3, t);
+    if (type != LUA_TNONE) {
+        lua_pushvalue(L, 2);
+        lua_rawsetp(L, 3, t);
+    }
+    return 1;
+}
+
+static int Lpb_encode_hook(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    int type = lua_type(L, 2);
+    if (t == NULL) luaL_argerror(L, 1, "type not found");
+    if (type != LUA_TNONE && type != LUA_TNIL && type != LUA_TFUNCTION)
+        typeerror(L, 2, "function");
+    lua_settop(L, 2);
+    lpb_pushenchooktable(L, LS);
+    lua_rawgetp(L, 3, t);
+    if (type != LUA_TNONE) {
+        lua_pushvalue(L, 2);
+        lua_rawsetp(L, 3, t);
+    }
+    return 1;
+}
+
+/*
+清除已加载的二进制定义数据
+@api protobuf.clear()
+@return nil 无返回值, 总是成功
+@usage
+-- 清除所有已加载的定义数据
+protobuf.clear()
+*/
+static int Lpb_clear(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    pb_State *S = (pb_State*)LS->state;
+    pb_Type *t;
+    if (lua_isnoneornil(L, 1)) {
+        pb_free(&LS->local), pb_init(&LS->local);
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->defs_index);
+        LS->defs_index = LUA_NOREF;
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->enc_hooks_index);
+        LS->enc_hooks_index = LUA_NOREF;
+        luaL_unref(L, LUA_REGISTRYINDEX, LS->dec_hooks_index);
+        LS->dec_hooks_index = LUA_NOREF;
+        return 0;
+    }
+    LS->state = &LS->local;
+    t = (pb_Type*)lpb_type(LS, lpb_checkslice(L, 1));
+    if (lua_isnoneornil(L, 2)) pb_deltype(&LS->local, t);
+    else pb_delfield(&LS->local, t, (pb_Field*)lpb_field(L, 2, t));
+    LS->state = S;
+    lpb_cleardefmeta(L, LS, t);
+    return 0;
+}
+
+static int Lpb_typefmt(lua_State *L) {
+    pb_Slice s = lpb_checkslice(L, 1);
+    const char *r = NULL;
+    char buf[2] = {0};
+    int type;
+    if (pb_len(s) == 1)
+        r = pb_typename(type = lpb_typefmt(*s.p), "!");
+    else if (lpb_type(lpb_lstate(L), s))
+        r = "message", type = PB_TBYTES;
+    else if ((type = pb_typebyname(s.p, PB_Tmessage)) != PB_Tmessage) {
+        switch (type) {
+#define X(name,type,fmt) case PB_T##name: buf[0] = fmt, r = buf; break;
+            PB_TYPES(X)
+#undef  X
+        }
+        type = pb_wtypebytype(type);
+    } else if ((type = pb_wtypebyname(s.p, PB_Tmessage)) != PB_Tmessage) {
+        switch (type) {
+#define X(id,name,fmt) case PB_T##id: buf[0] = fmt, r = buf; break;
+            PB_WIRETYPES(X)
+#undef  X
+        }
+    }
+    lua_pushstring(L, r ? r : "!");
+    lua_pushinteger(L, type);
+    return 2;
+}
+
+
+/* protobuf encode */
+
+typedef struct lpb_Env {
+    lua_State *L;
+    lpb_State *LS;
+    pb_Buffer *b;
+    pb_Slice *s;
+} lpb_Env;
+
+static void lpbE_encode (lpb_Env *e, const pb_Type *t, int idx);
+
+static void lpb_checktable(lua_State *L, const pb_Field *f, int idx) {
+    argcheck(L, lua_istable(L, idx),
+            2, "table expected at field '%s', got %s",
+            (const char*)f->name, luaL_typename(L, idx));
+}
+
+static void lpb_useenchooks(lua_State *L, lpb_State *LS, const pb_Type *t) {
+    lpb_pushenchooktable(L, LS);
+    if (lua53_rawgetp(L, -1, t) != LUA_TNIL) {
+        lua_pushvalue(L, -3);
+        lua_call(L, 1, 1);
+        if (!lua_isnil(L, -1)) {
+            lua_pushvalue(L, -1);
+            lua_replace(L, -4);
+        }
+    }
+    lua_pop(L, 2);
+}
+
+static void lpbE_enum(lpb_Env *e, const pb_Field *f, int idx) {
+    lua_State *L = e->L;
+    pb_Buffer *b = e->b;
+    const pb_Field *ev;
+    int type = lua_type(L, idx);
+    if (type == LUA_TNUMBER)
+        pb_addvarint64(b, (uint64_t)lua_tonumber(L, idx));
+    else if ((ev = pb_fname(f->type,
+                    lpb_name(e->LS, lpb_toslice(L, idx)))) != NULL)
+        pb_addvarint32(b, ev->number);
+    else if (type != LUA_TSTRING)
+        argcheck(L, 0, 2, "number/string expected at field '%s', got %s",
+                (const char*)f->name, luaL_typename(L, idx));
+    else
+        argcheck(L, 0, 2, "can not encode unknown enum '%s' at field '%s'",
+                lua_tostring(L, -1), (const char*)f->name);
+}
+
+static void lpbE_field(lpb_Env *e, const pb_Field *f, size_t *plen, int idx) {
+    lua_State *L = e->L;
+    pb_Buffer *b = e->b;
+    size_t len;
+    int ltype;
+    if (plen) *plen = 0;
+    switch (f->type_id) {
+    case PB_Tenum:
+        if (e->LS->use_enc_hooks) lpb_useenchooks(L, e->LS, f->type);
+        lpbE_enum(e, f, idx);
+        break;
+
+    case PB_Tmessage:
+        if (e->LS->use_enc_hooks) lpb_useenchooks(L, e->LS, f->type);
+        lpb_checktable(L, f, idx);
+        len = pb_bufflen(b);
+        lpbE_encode(e, f->type, idx);
+        lpb_addlength(L, b, len);
+        break;
+
+    default:
+        ltype = lpb_addtype(L, b, idx, f->type_id, plen);
+        argcheck(L, ltype == 0,
+                2, "%s expected for field '%s', got %s",
+                lua_typename(L, ltype),
+                (const char*)f->name, luaL_typename(L, idx));
+    }
+}
+
+static void lpbE_tagfield(lpb_Env *e, const pb_Field *f, int ignorezero, int idx) {
+    size_t hlen = pb_addvarint32(e->b,
+            pb_pair(f->number, pb_wtypebytype(f->type_id)));
+    size_t ignoredlen;
+    lpbE_field(e, f, &ignoredlen, idx);
+    if (!e->LS->encode_default_values && ignoredlen != 0 && ignorezero)
+        e->b->size -= (unsigned)(ignoredlen + hlen);
+}
+
+static void lpbE_map(lpb_Env *e, const pb_Field *f, int idx) {
+    lua_State *L = e->L;
+    const pb_Field *kf = pb_field(f->type, 1);
+    const pb_Field *vf = pb_field(f->type, 2);
+    if (kf == NULL || vf == NULL) return;
+    lpb_checktable(L, f, idx);
+    lua_pushnil(L);
+    while (lua_next(L, lpb_relindex(idx, 1))) {
+        size_t len;
+        pb_addvarint32(e->b, pb_pair(f->number, PB_TBYTES));
+        len = pb_bufflen(e->b);
+        lpbE_tagfield(e, kf, 1, -2);
+        lpbE_tagfield(e, vf, 1, -1);
+        lpb_addlength(L, e->b, len);
+
+        lua_pop(L, 1);
+    }
+}
+
+static void lpbE_repeated(lpb_Env *e, const pb_Field *f, int idx) {
+    lua_State *L = e->L;
+    pb_Buffer *b = e->b;
+    int i;
+    lpb_checktable(L, f, idx);
+
+    if (f->packed) {
+        unsigned len, bufflen = pb_bufflen(b);
+        pb_addvarint32(b, pb_pair(f->number, PB_TBYTES));
+        len = pb_bufflen(b);
+        for (i = 1; lua53_rawgeti(L, idx, i) != LUA_TNIL; ++i) {
+            lpbE_field(e, f, NULL, -1);
+            lua_pop(L, 1);
+        }
+        if (i == 1 && !e->LS->encode_default_values)
+            pb_bufflen(b) = bufflen;
+        else
+            lpb_addlength(L, b, len);
+    } else {
+        for (i = 1; lua53_rawgeti(L, idx, i) != LUA_TNIL; ++i) {
+            lpbE_tagfield(e, f, 0, -1);
+            lua_pop(L, 1);
+        }
+    }
+    lua_pop(L, 1);
+}
+
+static void lpb_encode_onefield(lpb_Env *e, const pb_Type *t, const pb_Field *f, int idx) {
+    if (f->type && f->type->is_map)
+        lpbE_map(e, f, idx);
+    else if (f->repeated)
+        lpbE_repeated(e, f, idx);
+    else if (!f->type || !f->type->is_dead)
+        lpbE_tagfield(e, f, t->is_proto3 && !f->oneof_idx, idx);
+}
+
+static void lpbE_encode(lpb_Env *e, const pb_Type *t, int idx) {
+    lua_State *L = e->L;
+    luaL_checkstack(L, 3, "message too many levels");
+    if (e->LS->encode_order) {
+        const pb_Field *f = NULL;
+        while (pb_nextfield(t, &f)) {
+            if (lua53_getfield(L, idx, (const char*)f->name) != LUA_TNIL)
+                lpb_encode_onefield(e, t, f, -1);
+            lua_pop(L, 1);
+        }
+    } else {
+        lua_pushnil(L);
+        while (lua_next(L, lpb_relindex(idx, 1))) {
+            if (lua_type(L, -2) == LUA_TSTRING) {
+                const pb_Field *f =
+                    pb_fname(t, lpb_name(e->LS, lpb_toslice(L, -2)));
+                if (f != NULL) lpb_encode_onefield(e, t, f, -1);
+            }
+            lua_pop(L, 1);
+        }
+    }
+}
+
+/*
+编码protobuffs数据包
+@api protobuf.encode(tpname, data)
+@string 数据类型名称, 定义在pb文件中, 由protobuf.load加载
+@table 待编码数据, 必须是table, 内容符合pb文件里的定义
+@return string 编码后的数据,若失败会返回nil
+@usage
+-- 数据编码
+local tb = {
+    name = "wendal"
+}
+local pbdata = protobuf.encode("Person", tb)
+if pbdata then
+    -- 注意, 编码后的数据通常带不可见字符
+    log.info("protobuf", #pbdata, pbdata:toHex())
+end
+*/
+static int Lpb_encode(lua_State *L) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    lpb_Env e;
+    argcheck(L, t!=NULL, 1, "type '%s' does not exists", lua_tostring(L, 1));
+    luaL_checktype(L, 2, LUA_TTABLE);
+    e.L = L, e.LS = LS, e.b = test_buffer(L, 3);
+    if (e.b == NULL) pb_resetbuffer(e.b = &LS->buffer);
+    lua_pushvalue(L, 2);
+    if (e.LS->use_enc_hooks) lpb_useenchooks(L, e.LS, t);
+    lpbE_encode(&e, t, -1);
+    if (e.b != &LS->buffer)
+        lua_settop(L, 3);
+    else {
+        lua_pushlstring(L, pb_buffer(e.b), pb_bufflen(e.b));
+        pb_resetbuffer(e.b);
+    }
+    return 1;
+}
+
+static int lpbE_pack(lpb_Env* e, const pb_Type* t, int idx) {
+    unsigned i;
+    lua_State* L = e->L;
+    pb_Field** f = pb_sortfield((pb_Type*)t);
+    for (i = 0; i < t->field_count; i++) {
+        int index = idx + i;
+        if (!lua_isnoneornil(L, index)) {
+            lpb_encode_onefield(e, t, f[i], index);
+        }
+    }
+    return 0;
+}
+
+static int Lpb_pack(lua_State* L) {
+    lpb_State* LS = lpb_lstate(L);
+    const pb_Type* t = lpb_type(LS, lpb_checkslice(L, 1));
+    lpb_Env e;
+    int idx = 3;
+    e.L = L, e.LS = LS, e.b = test_buffer(L, 2);
+    if (e.b == NULL) {
+        idx = 2;
+        pb_resetbuffer(e.b = &LS->buffer);
+    }
+    lpbE_pack(&e, t, idx);
+    if (e.b != &LS->buffer)
+        lua_settop(L, 3);
+    else {
+        lua_pushlstring(L, pb_buffer(e.b), pb_bufflen(e.b));
+        pb_resetbuffer(e.b);
+    }
+    return 1;
+}
+
+/* protobuf decode */
+
+#define lpb_withinput(e,ns,stmt) ((e)->s = (ns), (stmt), (e)->s = s)
+
+static int lpbD_message(lpb_Env *e, const pb_Type *t);
+
+static void lpb_usedechooks(lua_State *L, lpb_State *LS, const pb_Type *t) {
+    lpb_pushdechooktable(L, LS);
+    if (lua53_rawgetp(L, -1, t) != LUA_TNIL) {
+        lua_pushvalue(L, -3);
+        lua_call(L, 1, 1);
+        if (!lua_isnil(L, -1)) {
+            lua_pushvalue(L, -1);
+            lua_replace(L, -4);
+        }
+    }
+    lua_pop(L, 2);
+}
+
+static void lpb_pushtypetable(lua_State *L, lpb_State *LS, const pb_Type *t) {
+    int mode = LS->encode_mode;
+    luaL_checkstack(L, 2, "too many levels");
+    lpb_newmsgtable(L, t);
+    switch (t->is_proto3 && mode == LPB_DEFDEF ? LPB_COPYDEF : mode) {
+    case LPB_COPYDEF:
+        lpb_setdeffields(L, LS, t, USE_FIELD|USE_REPEAT|USE_MESSAGE);
+        break;
+    case LPB_METADEF:
+        lpb_setdeffields(L, LS, t, USE_REPEAT|USE_MESSAGE);
+        lpb_pushdefmeta(L, LS, t);
+        lua_setmetatable(L, -2);
+        break;
+    default:
+        if (LS->decode_default_array || LS->decode_default_message)
+            lpb_setdeffields(L, LS, t, USE_REPEAT|USE_MESSAGE);
+        break;
+    }
+}
+
+static void lpb_fetchtable(lpb_Env *e, const pb_Field *f) {
+    lua_State *L = e->L;
+    if (lua53_getfield(L, -1, (const char*)f->name) == LUA_TNIL) {
+        lua_pop(L, 1);
+        lua_newtable(L);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -3, (const char*)f->name);
+    }
+}
+
+static void lpbD_rawfield(lpb_Env *e, const pb_Field *f) {
+    lua_State *L = e->L;
+    pb_Slice sv, *s = e->s;
+    const pb_Field *ev = NULL;
+    uint64_t u64;
+    switch (f->type_id) {
+    case PB_Tenum:
+        if (pb_readvarint64(s, &u64) == 0)
+            luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1);
+        if (!lpb_lstate(L)->enum_as_value)
+            ev = pb_field(f->type, (int32_t)u64);
+        if (ev) lua_pushstring(L, (const char*)ev->name);
+        else lpb_pushinteger(L, (lua_Integer)u64, lpb_lstate(L)->int64_mode);
+        if (e->LS->use_dec_hooks) lpb_usedechooks(L, e->LS, f->type);
+        break;
+
+    case PB_Tmessage:
+        lpb_readbytes(L, s, &sv);
+        if (f->type == NULL || f->type->is_dead)
+            lua_pushnil(L);
+        else {
+            lpb_pushtypetable(L, e->LS, f->type);
+            lpb_withinput(e, &sv, lpbD_message(e, f->type));
+        }
+        break;
+
+    default:
+        lpb_readtype(L, e->LS, f->type_id, s);
+    }
+}
+
+static void lpbD_checktype(lpb_Env *e, const pb_Field *f, uint32_t tag) {
+    if (pb_wtypebytype(f->type_id) == (int)pb_gettype(tag)) return;
+    luaL_error(e->L,
+            "type mismatch for %s%sfield '%s' at offset %d, "
+            "%s expected for type %s, got %s",
+            f->packed ? "packed " : "", f->repeated ? "repeated " : "",
+            (const char*)f->name,
+            pb_pos(*e->s)+1,
+            pb_wtypename(pb_wtypebytype(f->type_id), NULL),
+            pb_typename(f->type_id, NULL),
+            pb_wtypename(pb_gettype(tag), NULL));
+}
+
+static void lpbD_field(lpb_Env *e, const pb_Field *f, uint32_t tag) {
+    lpbD_checktype(e, f, tag);
+    lpbD_rawfield(e, f);
+}
+
+static void lpbD_map(lpb_Env *e, const pb_Field *f) {
+    lua_State *L = e->L;
+    pb_Slice p, *s = e->s;
+    int mask = 0, top = lua_gettop(L);
+    uint32_t tag;
+    lpb_readbytes(L, s, &p);
+    if (f->type == NULL) return;
+    lua_pushnil(L);
+    lua_pushnil(L);
+    while (pb_readvarint32(&p, &tag)) {
+        int n = pb_gettag(tag);
+        if (n == 1 || n == 2) {
+            mask |= n;
+            lpb_withinput(e, &p, lpbD_field(e, pb_field(f->type, n), tag));
+            lua_replace(L, top+n);
+        }
+    }
+    if (!(mask & 1) && lpb_pushdeffield(L, e->LS, pb_field(f->type, 1), 1))
+        lua_replace(L, top + 1), mask |= 1;
+    if (!(mask & 2) && lpb_pushdeffield(L, e->LS, pb_field(f->type, 2), 1))
+        lua_replace(L, top + 2), mask |= 2;
+    if (mask == 3) lua_rawset(L, -3);
+    else           lua_pop(L, 2);
+}
+
+static void lpbD_repeated(lpb_Env *e, const pb_Field *f, uint32_t tag) {
+    lua_State *L = e->L;
+    if (pb_gettype(tag) != PB_TBYTES
+            || (!f->packed && pb_wtypebytype(f->type_id) == PB_TBYTES)) {
+        lpbD_field(e, f, tag);
+        lua_rawseti(L, -2, (lua_Integer)lua_rawlen(L, -2) + 1);
+    } else {
+        int len = (int)lua_rawlen(L, -1);
+        pb_Slice p, *s = e->s;
+        lpb_readbytes(L, s, &p);
+        while (p.p < p.end) {
+            lpb_withinput(e, &p, lpbD_rawfield(e, f));
+            lua_rawseti(L, -2, ++len);
+        }
+    }
+}
+
+static int lpbD_message(lpb_Env *e, const pb_Type *t) {
+    lua_State *L = e->L;
+    pb_Slice *s = e->s;
+    uint32_t tag;
+    luaL_checkstack(L, t->field_count * 2, "not enough stack space for fields");
+    while (pb_readvarint32(s, &tag)) {
+        const pb_Field *f = pb_field(t, pb_gettag(tag));
+        if (f == NULL)
+            pb_skipvalue(s, tag);
+        else if (f->type && f->type->is_map) {
+            lpb_fetchtable(e, f);
+            lpbD_checktype(e, f, tag);
+            lpbD_map(e, f);
+            lua_pop(L, 1);
+        } else if (f->repeated) {
+            lpb_fetchtable(e, f);
+            lpbD_repeated(e, f, tag);
+            lua_pop(L, 1);
+        } else {
+            lua_pushstring(L, (const char*)f->name);
+            if (f->oneof_idx) {
+                lua_pushstring(L, (const char*)pb_oneofname(t, f->oneof_idx));
+                lua_pushvalue(L, -2);
+                lua_rawset(L, -4);
+            }
+            lpbD_field(e, f, tag);
+            lua_rawset(L, -3);
+        }
+    }
+    if (e->LS->use_dec_hooks) lpb_usedechooks(L, e->LS, t);
+    return 1;
+}
+
+static int lpbD_decode(lua_State *L, pb_Slice s, int start) {
+    lpb_State *LS = lpb_lstate(L);
+    const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1));
+    lpb_Env e;
+    argcheck(L, t!=NULL, 1, "type '%s' does not exists", lua_tostring(L, 1));
+    lua_settop(L, start);
+    if (!lua_istable(L, start)) {
+        lua_pop(L, 1);
+        lpb_pushtypetable(L, LS, t);
+    }
+    e.L = L, e.LS = LS, e.s = &s;
+    return lpbD_message(&e, t);
+}
+
+/*
+解码protobuffs数据包
+@api protobuf.decode(tpname, data)
+@string 数据类型名称, 定义在pb文件中, 由protobuf.load加载
+@string 待编码数据
+@return table 解码后的数据
+@usage
+-- 数据编码
+local tb = {
+    name = "wendal"
+}
+local pbdata = protobuf.encode("Person", tb)
+if pbdata then
+    -- 注意, 编码后的数据通常带不可见字符
+    log.info("protobuf", #pbdata, pbdata:toHex())
+end
+*/
+static int Lpb_decode(lua_State *L) {
+    return lpbD_decode(L, lua_isnoneornil(L, 2) ?
+            pb_lslice(NULL, 0) :
+            lpb_checkslice(L, 2), 3);
+}
+
+
+void lpb_pushunpackdef(lua_State* L, lpb_State* LS, const pb_Type* t, pb_Field** l, int top) {
+    unsigned int i;
+    int mode = LS->encode_mode;
+    mode = t->is_proto3 && mode == LPB_DEFDEF ? LPB_COPYDEF : mode;
+    if (mode != LPB_COPYDEF && mode != LPB_METADEF) return;
+
+    for (i = 0; i < t->field_count; i++) {
+        int idx = top + i + 1;
+        if (lua_isnoneornil(L, idx) && lpb_pushdeffield(L, LS, l[i], t->is_proto3)) {
+            lua_replace(L, idx);
+        }
+    }
+}
+
+static int lpb_unpackfield(lpb_Env *e, const pb_Field* f, uint32_t tag, int last) {
+    if (!f) {
+        pb_skipvalue(e->s, tag);
+        return 0;
+    }
+    if (f->type && f->type->is_map) {
+        lpbD_checktype(e, f, tag);
+        if (!last) lua_newtable(e->L);
+        lpbD_map(e, f);
+    }
+    else if (f->repeated) {
+        if (!last) lua_newtable(e->L);
+        lpbD_repeated(e, f, tag);
+    }
+    else {
+        lpbD_field(e, f, tag);
+    }
+    return f->sort_index;
+}
+
+static int lpbD_unpack(lpb_Env* e, const pb_Type* t) {
+    lua_State* L = e->L;
+    int top = lua_gettop(L);
+    uint32_t tag;
+    int last_index = 0;
+    unsigned int decode_count = 0;
+    pb_Field **list = pb_sortfield((pb_Type*)t);
+    lua_settop(L, top + t->field_count);
+    luaL_checkstack(L, t->field_count * 2, "not enough stack space for fields");
+    while (pb_readvarint32(e->s, &tag)) {
+        const pb_Field* f = pb_field(t, pb_gettag(tag));
+        if (last_index && (!f || f->sort_index != last_index)) {
+            decode_count++;
+            lua_replace(L, top + last_index);
+            last_index = 0;
+        }
+        last_index = lpb_unpackfield(e, f, tag, last_index);
+    }
+    if (last_index) {
+        decode_count++;
+        lua_replace(L, top + last_index);
+    }
+    if (decode_count != t->field_count) lpb_pushunpackdef(L, e->LS, t, list, top);
+    return t->field_count;
+}
+
+static int Lpb_unpack(lua_State* L) {
+    lpb_State* LS = lpb_lstate(L);
+    const pb_Type* t = lpb_type(LS, lpb_checkslice(L, 1));
+    pb_Slice s = lpb_checkslice(L, 2);
+    lpb_Env e;
+    e.L = L, e.LS = LS, e.s = &s;
+    argcheck(L, t != NULL, 1, "type '%s' does not exists", lua_tostring(L, 1));
+    return lpbD_unpack(&e, t);
+}
+
+/* pb module interface */
+
+static int Lpb_option(lua_State *L) {
+#define OPTS(X) \
+    X(0,  enum_as_name,         LS->enum_as_value = 0)               \
+    X(1,  enum_as_value,        LS->enum_as_value = 1)               \
+    X(2,  int64_as_number,      LS->int64_mode = LPB_NUMBER)         \
+    X(3,  int64_as_string,      LS->int64_mode = LPB_STRING)         \
+    X(4,  int64_as_hexstring,   LS->int64_mode = LPB_HEXSTRING)      \
+    X(5,  encode_order,         LS->encode_order = 1)                \
+    X(6,  no_encode_order,      LS->encode_order = 0)                \
+    X(7,  encode_default_values, LS->encode_default_values = 1)      \
+    X(8,  no_encode_default_values, LS->encode_default_values = 0)   \
+    X(9,  auto_default_values,  LS->encode_mode = LPB_DEFDEF)        \
+    X(10, no_default_values,    LS->encode_mode = LPB_NODEF)         \
+    X(11, use_default_values,   LS->encode_mode = LPB_COPYDEF)       \
+    X(12, use_default_metatable, LS->encode_mode = LPB_METADEF)      \
+    X(13, decode_default_array, LS->decode_default_array = 1)        \
+    X(14, no_decode_default_array, LS->decode_default_array = 0)     \
+    X(15, decode_default_message, LS->decode_default_message = 1)    \
+    X(16, no_decode_default_message, LS->decode_default_message = 0) \
+    X(17, enable_hooks,         LS->use_dec_hooks = 1)               \
+    X(18, disable_hooks,        LS->use_dec_hooks = 0)               \
+    X(19, enable_enchooks,      LS->use_enc_hooks = 1)               \
+    X(20, disable_enchooks,     LS->use_enc_hooks = 0)               \
+
+    static const char *opts[] = {
+#define X(ID,NAME,CODE) #NAME,
+        OPTS(X)
+#undef  X
+        NULL
+    };
+    lpb_State *LS = lpb_lstate(L);
+    switch (luaL_checkoption(L, 1, NULL, opts)) {
+#define X(ID,NAME,CODE) case ID: CODE; break;
+        OPTS(X)
+#undef  X
+    }
+    return 0;
+#undef  OPTS
+}
+
+/*
+LUALIB_API int luaopen_pb(lua_State *L) {
+    luaL_Reg libs[] = {
+#define ENTRY(name) { #name, Lpb_##name }
+        ENTRY(clear),
+        ENTRY(load),
+        ENTRY(loadfile),
+        ENTRY(encode),
+        ENTRY(decode),
+        ENTRY(types),
+        ENTRY(fields),
+        ENTRY(type),
+        ENTRY(field),
+        ENTRY(typefmt),
+        ENTRY(enum),
+        ENTRY(defaults),
+        ENTRY(hook),
+        ENTRY(encode_hook),
+        ENTRY(tohex),
+        ENTRY(fromhex),
+        ENTRY(result),
+        ENTRY(option),
+        ENTRY(state),
+        ENTRY(pack),
+        ENTRY(unpack),
+#undef  ENTRY
+        { NULL, NULL }
+    };
+    luaL_Reg meta[] = {
+        { "__gc", Lpb_delete },
+        { "setdefault", Lpb_state },
+        { NULL, NULL }
+    };
+    if (luaL_newmetatable(L, PB_STATE)) {
+        luaL_setfuncs(L, meta, 0);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -2, "__index");
+    }
+    luaL_newlib(L, libs);
+    return 1;
+}
+*/
+
+// static int Lpb_decode_unsafe(lua_State *L) {
+//     const char *data = (const char *)lua_touserdata(L, 2);
+//     size_t size = (size_t)luaL_checkinteger(L, 3);
+//     if (data == NULL) typeerror(L, 2, "userdata");
+//     return lpbD_decode(L, pb_lslice(data, size), 4);
+// }
+
+// static int Lpb_slice_unsafe(lua_State *L) {
+//     const char *data = (const char *)lua_touserdata(L, 1);
+//     size_t size = (size_t)luaL_checkinteger(L, 2);
+//     if (data == NULL) typeerror(L, 1, "userdata");
+//     return lpb_newslice(L, data, size);
+// }
+
+// static int Lpb_touserdata(lua_State *L) {
+//     pb_Slice s = lpb_toslice(L, 1);
+//     lua_pushlightuserdata(L, (void*)s.p);
+//     lua_pushinteger(L, pb_len(s));
+//     return 2;
+// }
+
+// static int Lpb_use(lua_State *L) {
+//     const char *opts[] = { "global", "local", NULL };
+//     lpb_State *LS = lpb_lstate(L);
+//     const pb_State *GS = global_state;
+//     switch (luaL_checkoption(L, 1, NULL, opts)) {
+//     case 0: if (GS) LS->state = GS; break;
+//     case 1: LS->state = &LS->local; break;
+//     }
+//     lua_pushboolean(L, GS != NULL);
+//     return 1;
+// }
+
+// LUALIB_API int luaopen_pb_unsafe(lua_State *L) {
+//     luaL_Reg libs[] = {
+//         { "decode",     Lpb_decode_unsafe },
+//         { "slice",      Lpb_slice_unsafe  },
+//         { "touserdata", Lpb_touserdata    },
+//         { "use",        Lpb_use           },
+//         { NULL, NULL }
+//     };
+//     luaL_newlib(L, libs);
+//     return 1;
+// }
+
+#include "rotable2.h"
+
+static const rotable_Reg_t reg_protobuf[] = {
+#define ENTRY(name) { #name, ROREG_FUNC(Lpb_##name) }
+        ENTRY(clear),
+        ENTRY(load),
+        // ENTRY(loadfile), // 暂不支持loadfile, 通过io.readFile中转一下吧
+        ENTRY(encode),
+        ENTRY(decode),
+        // ENTRY(types),
+        // ENTRY(fields),
+        // ENTRY(type),
+        // ENTRY(field),
+        // ENTRY(typefmt),
+        // ENTRY(enum),
+        // ENTRY(defaults),
+        // ENTRY(hook),
+        // ENTRY(encode_hook),
+        // ENTRY(tohex),
+        // ENTRY(fromhex),
+        // ENTRY(result),
+        // ENTRY(option),
+        // ENTRY(state),
+        // ENTRY(pack),
+        // ENTRY(unpack),
+#undef  ENTRY
+        { NULL, ROREG_INT(0) }
+    };
+
+LUALIB_API int luaopen_protobuf(lua_State *L) {
+    rotable2_newlib(L, reg_protobuf);
+    return 1;
+}
+
+PB_NS_END
+
+/* cc: flags+='-O3 -ggdb -pedantic -std=c90 -Wall -Wextra --coverage'
+ * maccc: flags+='-ggdb -shared -undefined dynamic_lookup' output='pb.so'
+ * win32cc: flags+='-s -mdll -DLUA_BUILD_AS_DLL ' output='pb.dll' libs+='-llua54' */
+

+ 1717 - 0
components/serialization/protobuf/pb.h

@@ -0,0 +1,1717 @@
+#ifndef pb_h
+#define pb_h
+
+#ifndef PB_NS_BEGIN
+# ifdef __cplusplus
+#   define PB_NS_BEGIN extern "C" {
+#   define PB_NS_END   }
+# else
+#   define PB_NS_BEGIN
+#   define PB_NS_END
+# endif
+#endif /* PB_NS_BEGIN */
+
+#ifndef PB_STATIC
+# if __GNUC__
+#   define PB_STATIC static __attribute((unused))
+# else
+#   define PB_STATIC static
+# endif
+#endif
+
+#ifdef PB_STATIC_API
+# ifndef PB_IMPLEMENTATION
+#  define PB_IMPLEMENTATION
+# endif
+# define PB_API PB_STATIC
+#endif
+
+#if !defined(PB_API) && defined(_WIN32)
+# ifdef PB_IMPLEMENTATION
+#  define PB_API __declspec(dllexport)
+# else
+#  define PB_API __declspec(dllimport)
+# endif
+#endif
+
+#ifndef PB_API
+# define PB_API extern
+#endif
+
+
+
+#include "luat_base.h"
+#include <stddef.h>
+// #include <limits.h>
+
+PB_NS_BEGIN
+
+
+/* types */
+
+#define PB_WIRETYPES(X)   /* X(id,    name,    fmt) */\
+    X(VARINT, "varint", 'v') X(64BIT, "64bit", 'q') X(BYTES, "bytes", 's') \
+    X(GSTART, "gstart", '!') X(GEND,  "gend",  '!') X(32BIT, "32bit", 'd') \
+
+#define PB_TYPES(X)          /* X(name,     type,     fmt) */\
+    X(double,   double,   'F')  X(float,    float,    'f') \
+    X(int64,    int64_t,  'I')  X(uint64,   uint64_t, 'U') \
+    X(int32,    int32_t,  'i')  X(fixed64,  uint64_t, 'X') \
+    X(fixed32,  uint32_t, 'x')  X(bool,     int,      'b') \
+    X(string,   pb_Slice, 't')  X(group,    pb_Slice, 'g') \
+    X(message,  pb_Slice, 'S')  X(bytes,    pb_Slice, 's') \
+    X(uint32,   uint32_t, 'u')  X(enum,     int32_t,  'v') \
+    X(sfixed32, int32_t,  'y')  X(sfixed64, int64_t,  'Y') \
+    X(sint32,   int32_t,  'j')  X(sint64,   int64_t,  'J') \
+
+typedef enum pb_WireType {
+#define X(id,name,fmt) PB_T##id,
+    PB_WIRETYPES(X)
+#undef  X
+    PB_TWIRECOUNT
+} pb_WireType;
+
+typedef enum pb_FieldType {
+    PB_TNONE,
+#define X(name,type,fmt) PB_T##name,
+    PB_TYPES(X)
+#undef  X
+    PB_TYPECOUNT
+} pb_FieldType;
+
+
+/* conversions */
+
+PB_API uint64_t pb_expandsig     (uint32_t v);
+PB_API uint32_t pb_encode_sint32 ( int32_t v);
+PB_API  int32_t pb_decode_sint32 (uint32_t v);
+PB_API uint64_t pb_encode_sint64 ( int64_t v);
+PB_API  int64_t pb_decode_sint64 (uint64_t v);
+PB_API uint32_t pb_encode_float  (float    v);
+PB_API float    pb_decode_float  (uint32_t v);
+PB_API uint64_t pb_encode_double (double   v);
+PB_API double   pb_decode_double (uint64_t v);
+
+
+/* decode */
+
+typedef struct pb_Slice { const char *p, *start, *end; } pb_Slice;
+#define pb_gettype(v)      ((v) &  7)
+#define pb_gettag(v)       ((v) >> 3)
+#define pb_pair(tag,type)  ((tag) << 3 | ((type) & 7))
+
+PB_API pb_Slice pb_slice  (const char *p);
+PB_API pb_Slice pb_lslice (const char *p, size_t len);
+
+PB_API size_t pb_pos (const pb_Slice s);
+PB_API size_t pb_len (const pb_Slice s);
+
+PB_API size_t pb_readvarint32 (pb_Slice *s, uint32_t *pv);
+PB_API size_t pb_readvarint64 (pb_Slice *s, uint64_t *pv);
+PB_API size_t pb_readfixed32  (pb_Slice *s, uint32_t *pv);
+PB_API size_t pb_readfixed64  (pb_Slice *s, uint64_t *pv);
+
+PB_API size_t pb_readslice (pb_Slice *s, size_t len, pb_Slice *pv);
+PB_API size_t pb_readbytes (pb_Slice *s, pb_Slice *pv);
+PB_API size_t pb_readgroup (pb_Slice *s, uint32_t tag, pb_Slice *pv);
+
+PB_API size_t pb_skipvarint (pb_Slice *s);
+PB_API size_t pb_skipbytes  (pb_Slice *s);
+PB_API size_t pb_skipslice  (pb_Slice *s, size_t len);
+PB_API size_t pb_skipvalue  (pb_Slice *s, uint32_t tag);
+
+PB_API const char *pb_wtypename (int wiretype, const char *def);
+PB_API const char *pb_typename  (int type,     const char *def);
+
+PB_API int pb_typebyname  (const char *name, int def);
+PB_API int pb_wtypebyname (const char *name, int def);
+PB_API int pb_wtypebytype (int type);
+
+
+/* encode */
+
+#define PB_SSO_SIZE (sizeof(pb_HeapBuffer))
+
+typedef struct pb_HeapBuffer {
+    unsigned capacity;
+    char    *buff;
+} pb_HeapBuffer;
+
+typedef struct pb_Buffer {
+    unsigned size : sizeof(unsigned)*CHAR_BIT - 1;
+    unsigned heap : 1;
+    union {
+        char buff[PB_SSO_SIZE];
+        pb_HeapBuffer h;
+    } u;
+} pb_Buffer;
+
+#define pb_onheap(b)     ((b)->heap)
+#define pb_bufflen(b)    ((b)->size)
+#define pb_buffer(b)     (pb_onheap(b) ? (b)->u.h.buff : (b)->u.buff)
+#define pb_addsize(b,sz) ((void)((b)->size += (unsigned)(sz)))
+
+PB_API void  pb_initbuffer   (pb_Buffer *b);
+PB_API void  pb_resetbuffer  (pb_Buffer *b);
+PB_API char *pb_prepbuffsize (pb_Buffer *b, size_t len);
+
+PB_API pb_Slice pb_result (const pb_Buffer *b);
+
+PB_API size_t pb_addvarint32 (pb_Buffer *b, uint32_t v);
+PB_API size_t pb_addvarint64 (pb_Buffer *b, uint64_t v);
+PB_API size_t pb_addfixed32  (pb_Buffer *b, uint32_t v);
+PB_API size_t pb_addfixed64  (pb_Buffer *b, uint64_t v);
+
+PB_API size_t pb_addslice  (pb_Buffer *b, pb_Slice s);
+PB_API size_t pb_addbytes  (pb_Buffer *b, pb_Slice s);
+PB_API size_t pb_addlength (pb_Buffer *b, size_t len);
+
+
+/* type info database state and name table */
+
+typedef struct pb_State pb_State;
+typedef struct pb_Name  pb_Name;
+typedef struct pb_Cache pb_Cache;
+
+PB_API void pb_init (pb_State *S);
+PB_API void pb_free (pb_State *S);
+
+PB_API pb_Name *pb_newname (pb_State *S, pb_Slice s, pb_Cache *cache);
+PB_API void     pb_delname (pb_State *S, pb_Name *name);
+PB_API pb_Name *pb_usename (pb_Name *name);
+
+PB_API const pb_Name *pb_name (const pb_State *S, pb_Slice s, pb_Cache *cache);
+
+
+/* type info */
+
+typedef struct pb_Type  pb_Type;
+typedef struct pb_Field pb_Field;
+
+#define PB_OK     0
+#define PB_ERROR  1
+#define PB_ENOMEM 2
+
+PB_API int pb_load (pb_State *S, pb_Slice *s);
+
+PB_API pb_Type  *pb_newtype  (pb_State *S, pb_Name *tname);
+PB_API void      pb_deltype  (pb_State *S, pb_Type *t);
+PB_API pb_Field *pb_newfield (pb_State *S, pb_Type *t, pb_Name *fname, int32_t number);
+PB_API void      pb_delfield (pb_State *S, pb_Type *t, pb_Field *f);
+
+PB_API const pb_Type  *pb_type  (const pb_State *S, const pb_Name *tname);
+PB_API const pb_Field *pb_fname (const pb_Type *t,  const pb_Name *tname);
+PB_API const pb_Field *pb_field (const pb_Type *t,  int32_t number);
+
+PB_API const pb_Name *pb_oneofname (const pb_Type *t, int oneof_index);
+
+PB_API int pb_nexttype  (const pb_State *S, const pb_Type **ptype);
+PB_API int pb_nextfield (const pb_Type *t, const pb_Field **pfield);
+
+
+/* util: memory pool */
+
+#define PB_POOLSIZE 4096
+
+typedef struct pb_Pool {
+    void  *pages;
+    void  *freed;
+    size_t obj_size;
+} pb_Pool;
+
+PB_API void pb_initpool (pb_Pool *pool, size_t obj_size);
+PB_API void pb_freepool (pb_Pool *pool);
+
+PB_API void *pb_poolalloc (pb_Pool *pool);
+PB_API void  pb_poolfree  (pb_Pool *pool, void *obj);
+
+/* util: hash table */
+
+typedef struct pb_Table pb_Table;
+typedef struct pb_Entry pb_Entry;
+typedef ptrdiff_t pb_Key;
+
+PB_API void pb_inittable (pb_Table *t, size_t entrysize);
+PB_API void pb_freetable (pb_Table *t);
+
+PB_API size_t pb_resizetable (pb_Table *t, size_t size);
+
+PB_API pb_Entry *pb_gettable (const pb_Table *t, pb_Key key);
+PB_API pb_Entry *pb_settable (pb_Table *t, pb_Key key);
+
+PB_API int pb_nextentry (const pb_Table *t, const pb_Entry **pentry);
+
+struct pb_Table {
+    unsigned  size;
+    unsigned  lastfree;
+    unsigned  entry_size : sizeof(unsigned)*CHAR_BIT - 1;
+    unsigned  has_zero   : 1;
+    pb_Entry *hash;
+};
+
+struct pb_Entry {
+    ptrdiff_t next;
+    pb_Key    key;
+};
+
+
+/* fields */
+
+#define PB_CACHE_SIZE  (53)
+
+typedef struct pb_NameEntry {
+    struct pb_NameEntry *next;
+    unsigned hash     : 32;
+    unsigned length   : 16;
+    unsigned refcount : 16;
+} pb_NameEntry;
+
+typedef struct pb_NameTable {
+    size_t         size;
+    size_t         count;
+    pb_NameEntry **hash;
+} pb_NameTable;
+
+typedef struct pb_CacheSlot {
+    const char *name;
+    unsigned    hash;
+} pb_CacheSlot;
+
+struct pb_Cache {
+    pb_CacheSlot slots[PB_CACHE_SIZE][2];
+    unsigned     hash;
+};
+
+struct pb_State {
+    pb_NameTable nametable;
+    pb_Table     types;
+    pb_Pool      typepool;
+    pb_Pool      fieldpool;
+};
+
+struct pb_Field {
+    pb_Name *name;
+    pb_Type *type;
+    pb_Name *default_value;
+    int32_t  number;
+    int32_t  sort_index;
+    unsigned oneof_idx : 24;
+    unsigned type_id   : 5; /* PB_T* enum */
+    unsigned repeated  : 1;
+    unsigned packed    : 1;
+    unsigned scalar    : 1;
+};
+
+struct pb_Type {
+    pb_Name    *name;
+    const char *basename;
+    pb_Field **field_sort;
+    pb_Table field_tags;
+    pb_Table field_names;
+    pb_Table oneof_index;
+    unsigned oneof_count; /* extra field count from oneof entries */
+    unsigned oneof_field; /* extra field in oneof declarations */
+    unsigned field_count : 28;
+    unsigned is_enum   : 1;
+    unsigned is_map    : 1;
+    unsigned is_proto3 : 1;
+    unsigned is_dead   : 1;
+};
+
+
+PB_NS_END
+
+#endif /* pb_h */
+
+
+#if defined(PB_IMPLEMENTATION) && !defined(pb_implemented)
+#define pb_implemented
+
+#define PB_MAX_SIZET          ((unsigned)~0 - 100)
+#define PB_MAX_HASHSIZE       ((unsigned)~0 - 100)
+#define PB_MIN_STRTABLE_SIZE  16
+#define PB_MIN_HASHTABLE_SIZE 8
+#define PB_HASHLIMIT          5
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+PB_NS_BEGIN
+
+
+/* conversions */
+
+PB_API uint32_t pb_encode_sint32(int32_t value)
+{ return ((uint32_t)value << 1) ^ -(value < 0); }
+
+PB_API int32_t pb_decode_sint32(uint32_t value)
+{ return (value >> 1) ^ -(int32_t)(value & 1); }
+
+PB_API uint64_t pb_encode_sint64(int64_t value)
+{ return ((uint64_t)value << 1) ^ -(value < 0); }
+
+PB_API int64_t pb_decode_sint64(uint64_t value)
+{ return (value >> 1) ^ -(int64_t)(value & 1); }
+
+PB_API uint64_t pb_expandsig(uint32_t value)
+{ uint64_t m = (uint64_t)1U << 31; return (value ^ m) - m; }
+
+PB_API uint32_t pb_encode_float(float value)
+{ union { uint32_t u32; float f; } u; u.f = value; return u.u32; }
+
+PB_API float pb_decode_float(uint32_t value)
+{ union { uint32_t u32; float f; } u; u.u32 = value; return u.f; }
+
+PB_API uint64_t pb_encode_double(double value)
+{ union { uint64_t u64; double d; } u; u.d = value; return u.u64; }
+
+PB_API double pb_decode_double(uint64_t value)
+{ union { uint64_t u64; double d; } u; u.u64 = value; return u.d; }
+
+
+/* decode */
+
+PB_API pb_Slice pb_slice(const char *s)
+{ return s ? pb_lslice(s, strlen(s)) : pb_lslice(NULL, 0); }
+
+PB_API size_t pb_pos(const pb_Slice s) { return s.p - s.start; }
+PB_API size_t pb_len(const pb_Slice s) { return s.end - s.p; }
+
+static size_t pb_readvarint_slow(pb_Slice *s, uint64_t *pv) {
+    const char *p = s->p;
+    uint64_t n = 0;
+    int i = 0;
+    while (s->p < s->end && i < 10) {
+        int b = *s->p++;
+        n |= ((uint64_t)b & 0x7F) << (7*i++);
+        if ((b & 0x80) == 0) {
+            *pv = n;
+            return i;
+        }
+    }
+    s->p = p;
+    return 0;
+}
+
+static size_t pb_readvarint32_fallback(pb_Slice *s, uint32_t *pv) {
+    const uint8_t *p = (const uint8_t*)s->p, *o = p;
+    uint32_t b, n;
+    for (;;) {
+        n = *p++ - 0x80, n += (b = *p++) <<  7; if (!(b & 0x80)) break;
+        n -= 0x80 <<  7, n += (b = *p++) << 14; if (!(b & 0x80)) break;
+        n -= 0x80 << 14, n += (b = *p++) << 21; if (!(b & 0x80)) break;
+        n -= 0x80 << 21, n += (b = *p++) << 28; if (!(b & 0x80)) break;
+        /* n -= 0x80 << 28; */
+        if (!(*p++ & 0x80)) break;
+        if (!(*p++ & 0x80)) break;
+        if (!(*p++ & 0x80)) break;
+        if (!(*p++ & 0x80)) break;
+        if (!(*p++ & 0x80)) break;
+        return 0;
+    }
+    *pv = n;
+    s->p = (const char*)p;
+    return p - o;
+}
+
+static size_t pb_readvarint64_fallback(pb_Slice *s, uint64_t *pv) {
+    const uint8_t *p = (const uint8_t*)s->p, *o = p;
+    uint32_t b, n1, n2 = 0, n3 = 0;
+    for (;;) {
+        n1 = *p++ - 0x80, n1 += (b = *p++) <<  7; if (!(b & 0x80)) break;
+        n1 -= 0x80 <<  7, n1 += (b = *p++) << 14; if (!(b & 0x80)) break;
+        n1 -= 0x80 << 14, n1 += (b = *p++) << 21; if (!(b & 0x80)) break;
+        n1 -= 0x80 << 21, n2 += (b = *p++)      ; if (!(b & 0x80)) break;
+        n2 -= 0x80      , n2 += (b = *p++) <<  7; if (!(b & 0x80)) break;
+        n2 -= 0x80 <<  7, n2 += (b = *p++) << 14; if (!(b & 0x80)) break;
+        n2 -= 0x80 << 14, n2 += (b = *p++) << 21; if (!(b & 0x80)) break;
+        n2 -= 0x80 << 21, n3 += (b = *p++)      ; if (!(b & 0x80)) break;
+        n3 -= 0x80      , n3 += (b = *p++) <<  7; if (!(b & 0x80)) break;
+        return 0;
+    }
+    *pv = n1 | ((uint64_t)n2 << 28) | ((uint64_t)n3 << 56);
+    s->p = (const char*)p;
+    return p - o;
+}
+
+PB_API pb_Slice pb_lslice(const char *s, size_t len) {
+    pb_Slice slice;
+    slice.start = slice.p = s;
+    slice.end = s + len;
+    return slice;
+}
+
+PB_API size_t pb_readvarint32(pb_Slice *s, uint32_t *pv) {
+    uint64_t u64;
+    size_t ret;
+    if (s->p >= s->end)  return 0;
+    if (!(*s->p & 0x80)) return *pv = *s->p++, 1;
+    if (pb_len(*s) >= 10 || !(s->end[-1] & 0x80))
+        return pb_readvarint32_fallback(s, pv);
+    if ((ret = pb_readvarint_slow(s, &u64)) != 0)
+        *pv = (uint32_t)u64;
+    return ret;
+}
+
+PB_API size_t pb_readvarint64(pb_Slice *s, uint64_t *pv) {
+    if (s->p >= s->end)  return 0;
+    if (!(*s->p & 0x80)) return *pv = *s->p++, 1;
+    if (pb_len(*s) >= 10 || !(s->end[-1] & 0x80))
+        return pb_readvarint64_fallback(s, pv);
+    return pb_readvarint_slow(s, pv);
+}
+
+PB_API size_t pb_readfixed32(pb_Slice *s, uint32_t *pv) {
+    int i;
+    uint32_t n = 0;
+    if (s->p + 4 > s->end)
+        return 0;
+    for (i = 3; i >= 0; --i) {
+        n <<= 8;
+        n |= s->p[i] & 0xFF;
+    }
+    s->p += 4;
+    *pv = n;
+    return 4;
+}
+
+PB_API size_t pb_readfixed64(pb_Slice *s, uint64_t *pv) {
+    int i;
+    uint64_t n = 0;
+    if (s->p + 8 > s->end)
+        return 0;
+    for (i = 7; i >= 0; --i) {
+        n <<= 8;
+        n |= s->p[i] & 0xFF;
+    }
+    s->p += 8;
+    *pv = n;
+    return 8;
+}
+
+PB_API size_t pb_readslice(pb_Slice *s, size_t len, pb_Slice *pv) {
+    if (pb_len(*s) < len)
+        return 0;
+    pv->start = s->start;
+    pv->p     = s->p;
+    pv->end   = s->p + len;
+    s->p = pv->end;
+    return len;
+}
+
+PB_API size_t pb_readbytes(pb_Slice *s, pb_Slice *pv) {
+    const char *p = s->p;
+    uint64_t len;
+    if (pb_readvarint64(s, &len) == 0 || pb_len(*s) < len) {
+        s->p = p;
+        return 0;
+    }
+    pv->start = s->start;
+    pv->p   = s->p;
+    pv->end = s->p + len;
+    s->p = pv->end;
+    return s->p - p;
+}
+
+PB_API size_t pb_readgroup(pb_Slice *s, uint32_t tag, pb_Slice *pv) {
+    const char *p = s->p;
+    uint32_t newtag = 0;
+    size_t count;
+    assert(pb_gettype(tag) == PB_TGSTART);
+    while ((count = pb_readvarint32(s, &newtag)) != 0) {
+        if (pb_gettype(newtag) == PB_TGEND) {
+            if (pb_gettag(newtag) != pb_gettag(tag))
+                break;
+            pv->start = s->start;
+            pv->p = p;
+            pv->end = s->p - count;
+            return s->p - p;
+        }
+        if (pb_skipvalue(s, newtag) == 0) break;
+    }
+    s->p = p;
+    return 0;
+}
+
+PB_API size_t pb_skipvalue(pb_Slice *s, uint32_t tag) {
+    const char *p = s->p;
+    size_t ret = 0;
+    pb_Slice data;
+    switch (pb_gettype(tag)) {
+    default: break;
+    case PB_TVARINT: ret = pb_skipvarint(s); break;
+    case PB_T64BIT:  ret = pb_skipslice(s, 8); break;
+    case PB_TBYTES:  ret = pb_skipbytes(s); break;
+    case PB_T32BIT:  ret = pb_skipslice(s, 4); break;
+    case PB_TGSTART: ret = pb_readgroup(s, tag, &data); break;
+    }
+    if (!ret) s->p = p;
+    return ret;
+}
+
+PB_API size_t pb_skipvarint(pb_Slice *s) {
+    const char *p = s->p, *op = p;
+    while (p < s->end && (*p & 0x80) != 0) ++p;
+    if (p >= s->end) return 0;
+    s->p = ++p;
+    return p - op;
+}
+
+PB_API size_t pb_skipbytes(pb_Slice *s) {
+    const char *p = s->p;
+    uint64_t var;
+    if (!pb_readvarint64(s, &var)) return 0;
+    if (pb_len(*s) < var) {
+        s->p = p;
+        return 0;
+    }
+    s->p += var;
+    return s->p - p;
+}
+
+PB_API size_t pb_skipslice(pb_Slice *s, size_t len) {
+    if (s->p + len > s->end) return 0;
+    s->p += len;
+    return len;
+}
+
+PB_API int pb_wtypebytype(int type) {
+    switch (type) {
+    case PB_Tdouble:    return PB_T64BIT;
+    case PB_Tfloat:     return PB_T32BIT;
+    case PB_Tint64:     return PB_TVARINT;
+    case PB_Tuint64:    return PB_TVARINT;
+    case PB_Tint32:     return PB_TVARINT;
+    case PB_Tfixed64:   return PB_T64BIT;
+    case PB_Tfixed32:   return PB_T32BIT;
+    case PB_Tbool:      return PB_TVARINT;
+    case PB_Tstring:    return PB_TBYTES;
+    case PB_Tmessage:   return PB_TBYTES;
+    case PB_Tbytes:     return PB_TBYTES;
+    case PB_Tuint32:    return PB_TVARINT;
+    case PB_Tenum:      return PB_TVARINT;
+    case PB_Tsfixed32:  return PB_T32BIT;
+    case PB_Tsfixed64:  return PB_T64BIT;
+    case PB_Tsint32:    return PB_TVARINT;
+    case PB_Tsint64:    return PB_TVARINT;
+    default:            return PB_TWIRECOUNT;
+    }
+}
+
+PB_API const char *pb_wtypename(int wiretype, const char *def) {
+    switch (wiretype) {
+#define X(id,name,fmt) case PB_T##id: return name;
+        PB_WIRETYPES(X)
+#undef  X
+    default: return def ? def : "<unknown>";
+    }
+}
+
+PB_API const char *pb_typename(int type, const char *def) {
+    switch (type) {
+#define X(name,type,fmt) case PB_T##name: return #name;
+        PB_TYPES(X)
+#undef  X
+    default: return def ? def : "<unknown>";
+    }
+}
+
+PB_API int pb_typebyname(const char *name, int def) {
+    static struct entry { const char *name; int value; } names[] = {
+#define X(name,type,fmt) { #name, PB_T##name },
+        PB_TYPES(X)
+#undef  X
+        { NULL, 0 }
+    };
+    struct entry *p;
+    for (p = names; p->name != NULL; ++p)
+        if (strcmp(p->name, name) == 0)
+            return p->value;
+    return def;
+}
+
+PB_API int pb_wtypebyname(const char *name, int def) {
+    static struct entry { const char *name; int value; } names[] = {
+#define X(id,name,fmt) { name, PB_T##id },
+        PB_WIRETYPES(X)
+#undef  X
+        { NULL, 0 }
+    };
+    struct entry *p;
+    for (p = names; p->name != NULL; ++p)
+        if (strcmp(p->name, name) == 0)
+            return p->value;
+    return def;
+}
+
+
+/* encode */
+
+PB_API pb_Slice pb_result(const pb_Buffer *b)
+{ pb_Slice slice = pb_lslice(pb_buffer(b), b->size); return slice; }
+
+PB_API void pb_initbuffer(pb_Buffer *b)
+{ memset(b, 0, sizeof(pb_Buffer)); }
+
+PB_API void pb_resetbuffer(pb_Buffer *b)
+{ if (pb_onheap(b)) free(b->u.h.buff); pb_initbuffer(b); }
+
+static int pb_write32(char *buff, uint32_t n) {
+    int p, c = 0;
+    do {
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n;
+    } while (0);
+    return *buff++ = p, ++c;
+}
+
+static int pb_write64(char *buff, uint64_t n) {
+    int p, c = 0;
+    do {
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c;
+        p = n & 0x7F;
+    } while (0);
+    return *buff++ = p, ++c;
+}
+
+PB_API char *pb_prepbuffsize(pb_Buffer *b, size_t len) {
+    size_t capacity = pb_onheap(b) ? b->u.h.capacity : PB_SSO_SIZE;
+    if (b->size + len > capacity) {
+        char *newp, *oldp = pb_onheap(b) ? b->u.h.buff : NULL;
+        size_t expected = b->size + len;
+        size_t newsize  = PB_SSO_SIZE;
+        while (newsize < PB_MAX_SIZET/2 && newsize < expected)
+            newsize += newsize >> 1;
+        if (newsize < expected) return NULL;
+        if ((newp = (char*)realloc(oldp, newsize)) == NULL) return NULL;
+        if (!pb_onheap(b)) memcpy(newp, pb_buffer(b), b->size);
+        b->heap         = 1;
+        b->u.h.buff     = newp;
+        b->u.h.capacity = (unsigned)newsize;
+    }
+    return &pb_buffer(b)[b->size];
+}
+
+PB_API size_t pb_addslice(pb_Buffer *b, pb_Slice s) {
+    size_t len = pb_len(s);
+    char *buff = pb_prepbuffsize(b, len);
+    if (buff == NULL) return 0;
+    memcpy(buff, s.p, len);
+    pb_addsize(b, len);
+    return len;
+}
+
+PB_API size_t pb_addlength(pb_Buffer *b, size_t len) {
+    char buff[10], *s;
+    size_t bl, ml;
+    if ((bl = pb_bufflen(b)) < len)
+        return 0;
+    ml = pb_write64(buff, bl - len);
+    if (pb_prepbuffsize(b, ml) == NULL) return 0;
+    s = pb_buffer(b) + len;
+    memmove(s+ml, s, bl - len);
+    memcpy(s, buff, ml);
+    pb_addsize(b, ml);
+    return ml;
+}
+
+PB_API size_t pb_addbytes(pb_Buffer *b, pb_Slice s) {
+    size_t ret, len = pb_len(s);
+    if (pb_prepbuffsize(b, len+5) == NULL) return 0;
+    ret = pb_addvarint32(b, (uint32_t)len);
+    return ret + pb_addslice(b, s);
+}
+
+PB_API size_t pb_addvarint32(pb_Buffer *b, uint32_t n) {
+    char *buff = pb_prepbuffsize(b, 5);
+    size_t l;
+    if (buff == NULL) return 0;
+    pb_addsize(b, l = pb_write32(buff, n));
+    return l;
+}
+
+PB_API size_t pb_addvarint64(pb_Buffer *b, uint64_t n) {
+    char *buff = pb_prepbuffsize(b, 10);
+    size_t l;
+    if (buff == NULL) return 0;
+    pb_addsize(b, l = pb_write64(buff, n));
+    return l;
+}
+
+PB_API size_t pb_addfixed32(pb_Buffer *b, uint32_t n) {
+    char *ch = pb_prepbuffsize(b, 4);
+    if (ch == NULL) return 0;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch   = n & 0xFF;
+    pb_addsize(b, 4);
+    return 4;
+}
+
+PB_API size_t pb_addfixed64(pb_Buffer *b, uint64_t n) {
+    char *ch = pb_prepbuffsize(b, 8);
+    if (ch == NULL) return 0;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch++ = n & 0xFF; n >>= 8;
+    *ch   = n & 0xFF;
+    pb_addsize(b, 8);
+    return 8;
+}
+
+
+/* memory pool */
+
+PB_API void pb_initpool(pb_Pool *pool, size_t obj_size) {
+    memset(pool, 0, sizeof(pb_Pool));
+    pool->obj_size = obj_size;
+    assert(obj_size > sizeof(void*) && obj_size < PB_POOLSIZE/4);
+}
+
+PB_API void pb_freepool(pb_Pool *pool) {
+    void *page = pool->pages;
+    while (page) {
+        void *next = *(void**)((char*)page + PB_POOLSIZE - sizeof(void*));
+        free(page);
+        page = next;
+    }
+    pb_initpool(pool, pool->obj_size);
+}
+
+PB_API void *pb_poolalloc(pb_Pool *pool) {
+    void *obj = pool->freed;
+    if (obj == NULL) {
+        size_t objsize = pool->obj_size, offset;
+        void *newpage = malloc(PB_POOLSIZE);
+        if (newpage == NULL) return NULL;
+        offset = ((PB_POOLSIZE - sizeof(void*)) / objsize - 1) * objsize;
+        for (; offset > 0; offset -= objsize) {
+            void **entry = (void**)((char*)newpage + offset);
+            *entry = pool->freed, pool->freed = (void*)entry;
+        }
+        *(void**)((char*)newpage + PB_POOLSIZE - sizeof(void*)) = pool->pages;
+        pool->pages = newpage;
+        return newpage;
+    }
+    pool->freed = *(void**)obj;
+    return obj;
+}
+
+PB_API void pb_poolfree(pb_Pool *pool, void *obj)
+{ *(void**)obj = pool->freed, pool->freed = obj; }
+
+
+/* hash table */
+
+#define pbT_offset(a,b) ((char*)(a) - (char*)(b))
+#define pbT_index(a,b)  ((pb_Entry*)((char*)(a) + (b)))
+
+PB_API void pb_inittable(pb_Table *t, size_t entrysize)
+{ memset(t, 0, sizeof(pb_Table)), t->entry_size = (unsigned)entrysize; }
+
+PB_API void pb_freetable(pb_Table *t)
+{ free(t->hash); pb_inittable(t, t->entry_size); }
+
+static pb_Entry *pbT_hash(const pb_Table *t, pb_Key key) {
+    size_t h = ((size_t)key*2654435761U)&(t->size-1);
+    if (key && h == 0) h = 1;
+    return pbT_index(t->hash, h*t->entry_size);
+}
+
+static pb_Entry *pbT_newkey(pb_Table *t, pb_Key key) {
+    pb_Entry *mp, *on, *next, *f = NULL;
+    if (t->size == 0 && pb_resizetable(t, (size_t)t->size*2) == 0) return NULL;
+    if (key == 0) {
+        mp = t->hash;
+        t->has_zero = 1;
+    } else if ((mp = pbT_hash(t, key))->key != 0) {
+        while (t->lastfree > t->entry_size) {
+            pb_Entry *cur = pbT_index(t->hash, t->lastfree -= t->entry_size);
+            if (cur->key == 0 && cur->next == 0) { f = cur; break; }
+        }
+        if (f == NULL) return pb_resizetable(t, (size_t)t->size*2u) ?
+            pbT_newkey(t, key) : NULL;
+        if ((on = pbT_hash(t, mp->key)) != mp) {
+            while ((next = pbT_index(on, on->next)) != mp) on = next;
+            on->next = pbT_offset(f, on);
+            memcpy(f, mp, t->entry_size);
+            if (mp->next != 0) f->next += pbT_offset(mp, f), mp->next = 0;
+        } else {
+            if (mp->next != 0) f->next = pbT_offset(mp, f) + mp->next;
+            else assert(f->next == 0);
+            mp->next = pbT_offset(f, mp);
+            mp = f;
+        }
+    }
+    mp->key = key;
+    if (t->entry_size != sizeof(pb_Entry))
+        memset(mp+1, 0, t->entry_size - sizeof(pb_Entry));
+    return mp;
+}
+
+PB_API size_t pb_resizetable(pb_Table *t, size_t size) {
+    pb_Table nt = *t;
+    unsigned i, rawsize = t->size*t->entry_size;
+    unsigned newsize = PB_MIN_HASHTABLE_SIZE;
+    while (newsize < PB_MAX_HASHSIZE/t->entry_size && newsize < size)
+        newsize <<= 1;
+    if (newsize < size) return 0;
+    nt.size     = newsize;
+    nt.lastfree = nt.entry_size * newsize;
+    nt.hash     = (pb_Entry*)malloc(nt.lastfree);
+    if (nt.hash == NULL) return 0;
+    memset(nt.hash, 0, nt.lastfree);
+    for (i = 0; i < rawsize; i += t->entry_size) {
+        pb_Entry *olde = (pb_Entry*)((char*)t->hash + i);
+        pb_Entry *newe = pbT_newkey(&nt, olde->key);
+        if (nt.entry_size > sizeof(pb_Entry))
+            memcpy(newe+1, olde+1, nt.entry_size - sizeof(pb_Entry));
+    }
+    free(t->hash);
+    *t = nt;
+    return newsize;
+}
+
+PB_API pb_Entry *pb_gettable(const pb_Table *t, pb_Key key) {
+    pb_Entry *entry;
+    if (t == NULL || t->size == 0)
+        return NULL;
+    if (key == 0)
+        return t->has_zero ? t->hash : NULL;
+    for (entry = pbT_hash(t, key);
+            entry->key != key;
+            entry = pbT_index(entry, entry->next))
+        if (entry->next == 0) return NULL;
+    return entry;
+}
+
+PB_API pb_Entry *pb_settable(pb_Table *t, pb_Key key) {
+    pb_Entry *entry;
+    if ((entry = pb_gettable(t, key)) != NULL)
+        return entry;
+    return pbT_newkey(t, key);
+}
+
+PB_API int pb_nextentry(const pb_Table *t, const pb_Entry **pentry) {
+    size_t i = *pentry ? pbT_offset(*pentry, t->hash) : 0;
+    size_t size = (size_t)t->size*t->entry_size;
+    if (*pentry == NULL && t->has_zero) {
+        *pentry = t->hash;
+        return 1;
+    }
+    while (i += t->entry_size, i < size) {
+        pb_Entry *entry = pbT_index(t->hash, i);
+        if (entry->key != 0) {
+            *pentry = entry;
+            return 1;
+        }
+    }
+    *pentry = NULL;
+    return 0;
+}
+
+
+/* name table */
+
+static void pbN_init(pb_State *S)
+{ memset(&S->nametable, 0, sizeof(pb_NameTable)); }
+
+PB_API pb_Name *pb_usename(pb_Name *name)
+{ if (name != NULL) ++((pb_NameEntry*)name-1)->refcount; return name; }
+
+static void pbN_free(pb_State *S) {
+    pb_NameTable *nt = &S->nametable;
+    size_t i;
+    for (i = 0; i < nt->size; ++i) {
+        pb_NameEntry *ne = nt->hash[i];
+        while (ne != NULL) {
+            pb_NameEntry *next = ne->next;
+            free(ne);
+            ne = next;
+        }
+    }
+    free(nt->hash);
+    pbN_init(S);
+}
+
+static unsigned pbN_calchash(pb_Slice s) {
+    size_t len = pb_len(s);
+    unsigned h = (unsigned)len;
+    size_t step = (len >> PB_HASHLIMIT) + 1;
+    for (; len >= step; len -= step)
+        h ^= ((h<<5) + (h>>2) + (unsigned char)(s.p[len - 1]));
+    return h;
+}
+
+static size_t pbN_resize(pb_State *S, size_t size) {
+    pb_NameTable *nt = &S->nametable;
+    pb_NameEntry **hash;
+    size_t i, newsize = PB_MIN_STRTABLE_SIZE;
+    while (newsize < PB_MAX_HASHSIZE/sizeof(pb_NameEntry*) && newsize < size)
+        newsize <<= 1;
+    if (newsize < size) return 0;
+    hash = (pb_NameEntry**)malloc(newsize * sizeof(pb_NameEntry*));
+    if (hash == NULL) return 0;
+    memset(hash, 0, newsize * sizeof(pb_NameEntry*));
+    for (i = 0; i < nt->size; ++i) {
+        pb_NameEntry *entry = nt->hash[i];
+        while (entry != NULL) {
+            pb_NameEntry *next = entry->next;
+            pb_NameEntry **newh = &hash[entry->hash & (newsize - 1)];
+            entry->next = *newh, *newh = entry;
+            entry = next;
+        }
+    }
+    free(nt->hash);
+    nt->hash = hash;
+    nt->size = newsize;
+    return newsize;
+}
+
+static pb_NameEntry *pbN_newname(pb_State *S, pb_Slice s, unsigned hash) {
+    pb_NameTable *nt = &S->nametable;
+    pb_NameEntry **list, *newobj;
+    size_t len = pb_len(s);
+    if (nt->count >= nt->size && !pbN_resize(S, nt->size * 2)) return NULL;
+    list = &nt->hash[hash & (nt->size - 1)];
+    newobj = (pb_NameEntry*)malloc(sizeof(pb_NameEntry) + len + 1);
+    if (newobj == NULL) return NULL;
+    newobj->next     = *list;
+    newobj->length   = (unsigned)len;
+    newobj->refcount = 0;
+    newobj->hash     = hash;
+    memcpy(newobj+1, s.p, len);
+    ((char*)(newobj+1))[len] = '\0';
+    *list = newobj;
+    ++nt->count;
+    return newobj;
+}
+
+static void pbN_delname(pb_State *S, pb_NameEntry *name) {
+    pb_NameTable *nt = &S->nametable;
+    pb_NameEntry **list = &nt->hash[name->hash & (nt->size - 1)];
+    while (*list != NULL) {
+        if (*list != name)
+            list = &(*list)->next;
+        else {
+            *list = (*list)->next;
+            --nt->count;
+            free(name);
+            break;
+        }
+    }
+}
+
+static pb_NameEntry *pbN_getname(const pb_State *S, pb_Slice s, unsigned hash) {
+    const pb_NameTable *nt = &S->nametable;
+    size_t len = pb_len(s);
+    if (nt->hash) {
+        pb_NameEntry *entry = nt->hash[hash & (nt->size - 1)];
+        for (; entry != NULL; entry = entry->next)
+            if (entry->hash == hash && entry->length == len
+                    && memcmp(s.p, entry + 1, len) == 0)
+                return entry;
+    }
+    return NULL;
+}
+
+PB_API void pb_delname(pb_State *S, pb_Name *name) {
+    if (name != NULL) {
+        pb_NameEntry *ne = (pb_NameEntry*)name - 1;
+        if (ne->refcount <= 1) { pbN_delname(S, ne); return; }
+        --ne->refcount;
+    }
+}
+
+PB_API pb_Name *pb_newname(pb_State *S, pb_Slice s, pb_Cache *cache) {
+    pb_NameEntry *entry;
+    if (s.p == NULL) return NULL;
+    (void)cache;
+    assert(cache == NULL);
+    /* if (cache == NULL) */{
+        unsigned hash = pbN_calchash(s);
+        entry = pbN_getname(S, s, hash);
+        if (entry == NULL) entry = pbN_newname(S, s, hash);
+    }/* else {
+        pb_Name *name = (pb_Name*)pb_name(S, s, cache);
+        if (name) return pb_usename(name);
+        entry = pbN_newname(S, s, cache->hash);
+    }*/
+    return entry ? pb_usename((pb_Name*)(entry + 1)) : NULL;
+}
+
+PB_API const pb_Name *pb_name(const pb_State *S, pb_Slice s, pb_Cache *cache) {
+    pb_NameEntry *entry = NULL;
+    pb_CacheSlot *slot;
+    if (s.p == NULL) return NULL;
+    if (cache == NULL)
+        entry = pbN_getname(S, s, pbN_calchash(s));
+    else {
+        slot = cache->slots[((uintptr_t)s.p*2654435761U)%PB_CACHE_SIZE];
+        if (slot[0].name == s.p)
+            entry = pbN_getname(S, s, cache->hash = slot[0].hash);
+        else if (slot[1].name == s.p)
+            entry = pbN_getname(S, s, cache->hash = (++slot)[0].hash);
+        else
+            slot[1] = slot[0], slot[0].name = s.p;
+        if (entry == NULL) {
+            cache->hash = slot[0].hash = pbN_calchash(s);
+            entry = pbN_getname(S, s, slot[0].hash);
+        }
+    }
+    return entry ? (pb_Name*)(entry + 1) : NULL;
+}
+
+
+/* state */
+
+typedef struct pb_TypeEntry { pb_Entry entry; pb_Type *value; } pb_TypeEntry;
+typedef struct pb_FieldEntry { pb_Entry entry; pb_Field *value; } pb_FieldEntry;
+
+typedef struct pb_OneofEntry {
+    pb_Entry entry;
+    pb_Name *name;
+    unsigned index;
+} pb_OneofEntry;
+
+PB_API void pb_init(pb_State *S) {
+    memset(S, 0, sizeof(pb_State));
+    S->types.entry_size = sizeof(pb_TypeEntry);
+    pb_initpool(&S->typepool, sizeof(pb_Type));
+    pb_initpool(&S->fieldpool, sizeof(pb_Field));
+}
+
+PB_API void pb_free(pb_State *S) {
+    const pb_TypeEntry *te = NULL;
+    if (S == NULL) return;
+    while (pb_nextentry(&S->types, (const pb_Entry**)&te))
+        if (te->value != NULL) pb_deltype(S, te->value);
+    pb_freetable(&S->types);
+    pb_freepool(&S->typepool);
+    pb_freepool(&S->fieldpool);
+    pbN_free(S);
+}
+
+PB_API const pb_Type *pb_type(const pb_State *S, const pb_Name *tname) {
+    pb_TypeEntry *te = NULL;
+    if (S != NULL && tname != NULL)
+        te = (pb_TypeEntry*)pb_gettable(&S->types, (pb_Key)tname);
+    return te && !te->value->is_dead ? te->value : NULL;
+}
+
+PB_API const pb_Field *pb_fname(const pb_Type *t, const pb_Name *name) {
+    pb_FieldEntry *fe = NULL;
+    if (t != NULL && name != NULL)
+        fe = (pb_FieldEntry*)pb_gettable(&t->field_names, (pb_Key)name);
+    return fe ? fe->value : NULL;
+}
+
+PB_API const pb_Field *pb_field(const pb_Type *t, int32_t number) {
+    pb_FieldEntry *fe = NULL;
+    if (t != NULL) fe = (pb_FieldEntry*)pb_gettable(&t->field_tags, number);
+    return fe ? fe->value : NULL;
+}
+
+
+static int comp_field(const void* a, const void* b) {
+    return (*(const pb_Field**)a)->number - (*(const pb_Field**)b)->number;
+}
+
+PB_API pb_Field** pb_sortfield(pb_Type* t) {
+    if (!t->field_sort && t->field_count) {
+        int index = 0;
+        unsigned int i = 0;
+        const pb_Field* f = NULL;
+        pb_Field** list = malloc(sizeof(pb_Field*) * t->field_count);
+
+        assert(list);
+        while (pb_nextfield(t, &f)) {
+            list[index++] = (pb_Field*)f;
+        }
+
+        qsort(list, index, sizeof(pb_Field*), comp_field);
+        for (i = 0; i < t->field_count; i++) {
+            list[i]->sort_index = i + 1;
+        }
+        t->field_sort = list;
+    }
+
+    return t->field_sort;
+}
+
+PB_API const pb_Name *pb_oneofname(const pb_Type *t, int idx) {
+    pb_OneofEntry *oe = NULL;
+    if (t != NULL) oe = (pb_OneofEntry*)pb_gettable(&t->oneof_index, idx);
+    return oe ? oe->name : NULL;
+}
+
+PB_API int pb_nexttype(const pb_State *S, const pb_Type **ptype) {
+    const pb_TypeEntry *e = NULL;
+    if (S != NULL) {
+        if (*ptype != NULL)
+            e = (pb_TypeEntry*)pb_gettable(&S->types, (pb_Key)(*ptype)->name);
+        while (pb_nextentry(&S->types, (const pb_Entry**)&e))
+            if ((*ptype = e->value) != NULL && !(*ptype)->is_dead)
+                return 1;
+    }
+    *ptype = NULL;
+    return 0;
+}
+
+PB_API int pb_nextfield(const pb_Type *t, const pb_Field **pfield) {
+    const pb_FieldEntry *e = NULL;
+    if (t != NULL) {
+        if (*pfield != NULL)
+            e = (pb_FieldEntry*)pb_gettable(&t->field_tags, (*pfield)->number);
+        while (pb_nextentry(&t->field_tags, (const pb_Entry**)&e))
+            if ((*pfield = e->value) != NULL)
+                return 1;
+    }
+    *pfield = NULL;
+    return 0;
+}
+
+
+/* new type/field */
+
+static const char *pbT_basename(const char *tname) {
+    const char *end = tname + strlen(tname);
+    while (tname < end && *--end != '.')
+        ;
+    return *end != '.' ? end : end + 1;
+}
+
+static void pbT_inittype(pb_Type *t) {
+    memset(t, 0, sizeof(pb_Type));
+    pb_inittable(&t->field_names, sizeof(pb_FieldEntry));
+    pb_inittable(&t->field_tags, sizeof(pb_FieldEntry));
+    pb_inittable(&t->oneof_index, sizeof(pb_OneofEntry));
+}
+
+static void pbT_freefield(pb_State *S, pb_Field *f) {
+    pb_delname(S, f->default_value);
+    pb_delname(S, f->name);
+    pb_poolfree(&S->fieldpool, f);
+}
+
+PB_API pb_Type *pb_newtype(pb_State *S, pb_Name *tname) {
+    pb_TypeEntry *te;
+    pb_Type *t;
+    if (tname == NULL) return NULL;
+    te = (pb_TypeEntry*)pb_settable(&S->types, (pb_Key)tname);
+    if (te == NULL) return NULL;
+    if ((t = te->value) != NULL) return t->is_dead = 0, t;
+    if (!(t = (pb_Type*)pb_poolalloc(&S->typepool))) return NULL;
+    pbT_inittype(t);
+    t->name = tname;
+    t->basename = pbT_basename((const char*)tname);
+    return te->value = t;
+}
+
+PB_API void pb_delsort(pb_Type *t) {
+    if (t->field_sort) {
+        free(t->field_sort);
+        t->field_sort = NULL;
+    }
+}
+
+PB_API void pb_deltype(pb_State *S, pb_Type *t) {
+    pb_FieldEntry *nf = NULL;
+    pb_OneofEntry *ne = NULL;
+    if (S == NULL || t == NULL) return;
+    while (pb_nextentry(&t->field_names, (const pb_Entry**)&nf)) {
+        if (nf->value != NULL) {
+            pb_FieldEntry *of = (pb_FieldEntry*)pb_gettable(
+                    &t->field_tags, nf->value->number);
+            if (of && of->value == nf->value)
+                of->entry.key = 0, of->value = NULL;
+            pbT_freefield(S, nf->value);
+        }
+    }
+    while (pb_nextentry(&t->field_tags, (const pb_Entry**)&nf))
+        if (nf->value != NULL) pbT_freefield(S, nf->value);
+    while (pb_nextentry(&t->oneof_index, (const pb_Entry**)&ne))
+        pb_delname(S, ne->name);
+    pb_freetable(&t->field_tags);
+    pb_freetable(&t->field_names);
+    pb_freetable(&t->oneof_index);
+    t->oneof_field = 0, t->field_count = 0;
+    t->is_dead = 1;
+    pb_delsort(t);
+    /*pb_delname(S, t->name); */
+    /*pb_poolfree(&S->typepool, t); */
+}
+
+PB_API pb_Field *pb_newfield(pb_State *S, pb_Type *t, pb_Name *fname, int32_t number) {
+    pb_FieldEntry *nf, *tf;
+    pb_Field *f;
+    if (fname == NULL) return NULL;
+    nf = (pb_FieldEntry*)pb_settable(&t->field_names, (pb_Key)fname);
+    tf = (pb_FieldEntry*)pb_settable(&t->field_tags, number);
+    if (nf == NULL || tf == NULL) return NULL;
+    if ((f = nf->value) != NULL && tf->value == f) {
+        pb_delname(S, f->default_value);
+        f->default_value = NULL;
+        return f;
+    }
+    if (!(f = (pb_Field*)pb_poolalloc(&S->fieldpool))) return NULL;
+    memset(f, 0, sizeof(pb_Field));
+    f->name   = fname;
+    f->type   = t;
+    f->number = number;
+    if (nf->value && pb_field(t, nf->value->number) != nf->value)
+        pbT_freefield(S, nf->value), --t->field_count;
+    if (tf->value && pb_fname(t, tf->value->name) != tf->value)
+        pbT_freefield(S, tf->value), --t->field_count;
+    ++t->field_count;
+    pb_delsort(t);
+    return nf->value = tf->value = f;
+}
+
+PB_API void pb_delfield(pb_State *S, pb_Type *t, pb_Field *f) {
+    pb_FieldEntry *nf, *tf;
+    int count = 0;
+    if (S == NULL || t == NULL || f == NULL) return;
+    nf = (pb_FieldEntry*)pb_gettable(&t->field_names, (pb_Key)f->name);
+    tf = (pb_FieldEntry*)pb_gettable(&t->field_tags, (pb_Key)f->number);
+    if (nf && nf->value == f) nf->entry.key = 0, nf->value = NULL, ++count;
+    if (tf && tf->value == f) tf->entry.key = 0, tf->value = NULL, ++count;
+    if (count) pbT_freefield(S, f), --t->field_count;
+    pb_delsort(t);
+}
+
+
+/* .pb proto loader */
+
+typedef struct pb_Loader         pb_Loader;
+typedef struct pbL_FieldInfo     pbL_FieldInfo;
+typedef struct pbL_EnumValueInfo pbL_EnumValueInfo;
+typedef struct pbL_EnumInfo      pbL_EnumInfo;
+typedef struct pbL_TypeInfo      pbL_TypeInfo;
+typedef struct pbL_FileInfo      pbL_FileInfo;
+
+#define pbC(e)  do { int r = (e); if (r != PB_OK) return r; } while (0)
+#define pbCM(e) do { if ((e) == NULL) return PB_ENOMEM; } while (0)
+#define pbCE(e) do { if ((e) == NULL) return PB_ERROR;  } while (0)
+
+typedef struct pb_ArrayHeader {
+    unsigned count;
+    unsigned capacity;
+} pb_ArrayHeader;
+
+#define pbL_rawh(A)   ((pb_ArrayHeader*)(A) - 1)
+#define pbL_delete(A) ((A) ? (void)free(pbL_rawh(A)) : (void)0)
+#define pbL_count(A)  ((A) ? pbL_rawh(A)->count    : 0)
+#define pbL_add(A)    (pbL_grow((void**)&(A),sizeof(*(A)))==PB_OK ?\
+                       &(A)[pbL_rawh(A)->count++] : NULL)
+
+struct pb_Loader {
+    pb_Slice  s;
+    pb_Buffer b;
+    int       is_proto3;
+};
+
+/* parsers */
+
+struct pbL_EnumValueInfo {
+    pb_Slice name;
+    int32_t  number;
+};
+
+struct pbL_EnumInfo {
+    pb_Slice           name;
+    pbL_EnumValueInfo *value;
+};
+
+struct pbL_FieldInfo {
+    pb_Slice name;
+    pb_Slice type_name;
+    pb_Slice extendee;
+    pb_Slice default_value;
+    int32_t  number;
+    int32_t  label;
+    int32_t  type;
+    int32_t  oneof_index;
+    int32_t  packed;
+};
+
+struct pbL_TypeInfo {
+    pb_Slice       name;
+    int32_t        is_map;
+    pbL_FieldInfo *field;
+    pbL_FieldInfo *extension;
+    pbL_EnumInfo  *enum_type;
+    pbL_TypeInfo  *nested_type;
+    pb_Slice      *oneof_decl;
+};
+
+struct pbL_FileInfo {
+    pb_Slice       package;
+    pb_Slice       syntax;
+    pbL_EnumInfo  *enum_type;
+    pbL_TypeInfo  *message_type;
+    pbL_FieldInfo *extension;
+};
+
+static int pbL_readbytes(pb_Loader *L, pb_Slice *pv)
+{ return pb_readbytes(&L->s, pv) == 0 ? PB_ERROR : PB_OK; }
+
+static int pbL_beginmsg(pb_Loader *L, pb_Slice *pv)
+{ pb_Slice v; pbC(pbL_readbytes(L, &v)); *pv = L->s, L->s = v; return PB_OK; }
+
+static void pbL_endmsg(pb_Loader *L, pb_Slice *pv)
+{ L->s = *pv; }
+
+static int pbL_grow(void **pp, size_t objs) {
+    pb_ArrayHeader *nh, *h = *pp ? pbL_rawh(*pp) : NULL;
+    if (h == NULL || h->capacity <= h->count) {
+        size_t used = (h ? h->count : 0);
+        size_t size = used + 4, nsize = size + (size >> 1);
+        nh = nsize < size ? NULL :
+            (pb_ArrayHeader*)realloc(h, sizeof(pb_ArrayHeader)+nsize*objs);
+        if (nh == NULL) return PB_ENOMEM;
+        nh->count    = (unsigned)used;
+        nh->capacity = (unsigned)nsize;
+        *pp = nh + 1;
+        memset((char*)*pp + used*objs, 0, (nsize - used)*objs);
+    }
+    return PB_OK;
+}
+
+static int pbL_readint32(pb_Loader *L, int32_t *pv) {
+    uint32_t v;
+    if (pb_readvarint32(&L->s, &v) == 0) return PB_ERROR;
+    *pv = (int32_t)v;
+    return PB_OK;
+}
+
+static int pbL_FieldOptions(pb_Loader *L, pbL_FieldInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(2, PB_TVARINT): /* bool packed */
+            pbC(pbL_readint32(L, &info->packed)); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_FieldDescriptorProto(pb_Loader *L, pbL_FieldInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    info->packed = -1;
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* string name */
+            pbC(pbL_readbytes(L, &info->name)); break;
+        case pb_pair(3, PB_TVARINT): /* int32 number */
+            pbC(pbL_readint32(L, &info->number)); break;
+        case pb_pair(4, PB_TVARINT): /* Label label */
+            pbC(pbL_readint32(L, &info->label)); break;
+        case pb_pair(5, PB_TVARINT): /* Type type */
+            pbC(pbL_readint32(L, &info->type)); break;
+        case pb_pair(6, PB_TBYTES): /* string type_name */
+            pbC(pbL_readbytes(L, &info->type_name)); break;
+        case pb_pair(2, PB_TBYTES): /* string extendee */
+            pbC(pbL_readbytes(L, &info->extendee)); break;
+        case pb_pair(7, PB_TBYTES): /* string default_value */
+            pbC(pbL_readbytes(L, &info->default_value)); break;
+        case pb_pair(8, PB_TBYTES): /* FieldOptions options */
+            pbC(pbL_FieldOptions(L, info)); break;
+        case pb_pair(9, PB_TVARINT): /* int32 oneof_index */
+            pbC(pbL_readint32(L, &info->oneof_index));
+            ++info->oneof_index; break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_EnumValueDescriptorProto(pb_Loader *L, pbL_EnumValueInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* string name */
+            pbC(pbL_readbytes(L, &info->name)); break;
+        case pb_pair(2, PB_TVARINT): /* int32 number */
+            pbC(pbL_readint32(L, &info->number)); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_EnumDescriptorProto(pb_Loader *L, pbL_EnumInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* string name */
+            pbC(pbL_readbytes(L, &info->name)); break;
+        case pb_pair(2, PB_TBYTES): /* EnumValueDescriptorProto value */
+            pbC(pbL_EnumValueDescriptorProto(L, pbL_add(info->value))); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_MessageOptions(pb_Loader *L, pbL_TypeInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(7, PB_TVARINT): /* bool map_entry */
+            pbC(pbL_readint32(L, &info->is_map)); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_OneofDescriptorProto(pb_Loader *L, pbL_TypeInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* string name */
+            pbC(pbL_readbytes(L, pbL_add(info->oneof_decl))); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_DescriptorProto(pb_Loader *L, pbL_TypeInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* string name */
+            pbC(pbL_readbytes(L, &info->name)); break;
+        case pb_pair(2, PB_TBYTES): /* FieldDescriptorProto field */
+            pbC(pbL_FieldDescriptorProto(L, pbL_add(info->field))); break;
+        case pb_pair(6, PB_TBYTES): /* FieldDescriptorProto extension */
+            pbC(pbL_FieldDescriptorProto(L, pbL_add(info->extension))); break;
+        case pb_pair(3, PB_TBYTES): /* DescriptorProto nested_type */
+            pbC(pbL_DescriptorProto(L, pbL_add(info->nested_type))); break;
+        case pb_pair(4, PB_TBYTES): /* EnumDescriptorProto enum_type */
+            pbC(pbL_EnumDescriptorProto(L, pbL_add(info->enum_type))); break;
+        case pb_pair(8, PB_TBYTES): /* OneofDescriptorProto oneof_decl */
+            pbC(pbL_OneofDescriptorProto(L, info)); break;
+        case pb_pair(7, PB_TBYTES): /* MessageOptions options */
+            pbC(pbL_MessageOptions(L, info)); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_FileDescriptorProto(pb_Loader *L, pbL_FileInfo *info) {
+    pb_Slice s;
+    uint32_t tag;
+    pbCM(info); pbC(pbL_beginmsg(L, &s));
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(2, PB_TBYTES): /* string package */
+            pbC(pbL_readbytes(L, &info->package)); break;
+        case pb_pair(4, PB_TBYTES): /* DescriptorProto message_type */
+            pbC(pbL_DescriptorProto(L, pbL_add(info->message_type))); break;
+        case pb_pair(5, PB_TBYTES): /* EnumDescriptorProto enum_type */
+            pbC(pbL_EnumDescriptorProto(L, pbL_add(info->enum_type))); break;
+        case pb_pair(7, PB_TBYTES): /* FieldDescriptorProto extension */
+            pbC(pbL_FieldDescriptorProto(L, pbL_add(info->extension))); break;
+        case pb_pair(12, PB_TBYTES): /* string syntax */
+            pbC(pbL_readbytes(L, &info->syntax)); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    pbL_endmsg(L, &s);
+    return PB_OK;
+}
+
+static int pbL_FileDescriptorSet(pb_Loader *L, pbL_FileInfo **pfiles) {
+    uint32_t tag;
+    while (pb_readvarint32(&L->s, &tag)) {
+        switch (tag) {
+        case pb_pair(1, PB_TBYTES): /* FileDescriptorProto file */
+            pbC(pbL_FileDescriptorProto(L, pbL_add(*pfiles))); break;
+        default: if (pb_skipvalue(&L->s, tag) == 0) return PB_ERROR;
+        }
+    }
+    return PB_OK;
+}
+
+/* loader */
+
+static void pbL_delTypeInfo(pbL_TypeInfo *info) {
+    size_t i, count;
+    for (i = 0, count = pbL_count(info->nested_type); i < count; ++i)
+        pbL_delTypeInfo(&info->nested_type[i]);
+    for (i = 0, count = pbL_count(info->enum_type); i < count; ++i)
+        pbL_delete(info->enum_type[i].value);
+    pbL_delete(info->nested_type);
+    pbL_delete(info->enum_type);
+    pbL_delete(info->field);
+    pbL_delete(info->extension);
+    pbL_delete(info->oneof_decl);
+}
+
+static void pbL_delFileInfo(pbL_FileInfo *files) {
+    size_t i, count, j, jcount;
+    for (i = 0, count = pbL_count(files); i < count; ++i) {
+        for (j = 0, jcount = pbL_count(files[i].message_type); j < jcount; ++j)
+            pbL_delTypeInfo(&files[i].message_type[j]);
+        for (j = 0, jcount = pbL_count(files[i].enum_type); j < jcount; ++j)
+            pbL_delete(files[i].enum_type[j].value);
+        pbL_delete(files[i].message_type);
+        pbL_delete(files[i].enum_type);
+        pbL_delete(files[i].extension);
+    }
+    pbL_delete(files);
+}
+
+static int pbL_prefixname(pb_State *S, pb_Slice s, size_t *ps, pb_Loader *L, pb_Name **out) {
+    char *buff;
+    *ps = pb_bufflen(&L->b);
+    pbCM(buff = pb_prepbuffsize(&L->b, pb_len(s) + 1));
+    *buff = '.'; pb_addsize(&L->b, 1);
+    if (pb_addslice(&L->b, s) == 0) return PB_ENOMEM;
+    if (out) *out = pb_newname(S, pb_result(&L->b), NULL);
+    return PB_OK;
+}
+
+static int pbL_loadEnum(pb_State *S, pbL_EnumInfo *info, pb_Loader *L) {
+    size_t i, count, curr;
+    pb_Name *name;
+    pb_Type *t;
+    pbC(pbL_prefixname(S, info->name, &curr, L, &name));
+    pbCM(t = pb_newtype(S, name));
+    t->is_enum = 1;
+    for (i = 0, count = pbL_count(info->value); i < count; ++i) {
+        pbL_EnumValueInfo *ev = &info->value[i];
+        pbCE(pb_newfield(S, t, pb_newname(S, ev->name, NULL), ev->number));
+    }
+    L->b.size = (unsigned)curr;
+    return PB_OK;
+}
+
+static int pbL_loadField(pb_State *S, pbL_FieldInfo *info, pb_Loader *L, pb_Type *t) {
+    pb_Type  *ft = NULL;
+    pb_Field *f;
+    if (info->type == PB_Tmessage || info->type == PB_Tenum)
+        pbCE(ft = pb_newtype(S, pb_newname(S, info->type_name, NULL)));
+    if (t == NULL)
+        pbCE(t = pb_newtype(S, pb_newname(S, info->extendee, NULL)));
+    pbCE(f = pb_newfield(S, t, pb_newname(S, info->name, NULL), info->number));
+    f->default_value = pb_newname(S, info->default_value, NULL);
+    f->type      = ft;
+    if ((f->oneof_idx = info->oneof_index)) ++t->oneof_field;
+    f->type_id   = info->type;
+    f->repeated  = info->label == 3; /* repeated */
+    f->packed    = info->packed >= 0 ? info->packed : L->is_proto3 && f->repeated;
+    if (f->type_id >= 9 && f->type_id <= 12) f->packed = 0;
+    f->scalar = (f->type == NULL);
+    return PB_OK;
+}
+
+static int pbL_loadType(pb_State *S, pbL_TypeInfo *info, pb_Loader *L) {
+    size_t i, count, curr;
+    pb_Name *name;
+    pb_Type *t;
+    pbC(pbL_prefixname(S, info->name, &curr, L, &name));
+    pbCM(t = pb_newtype(S, name));
+    t->is_map = info->is_map, t->is_proto3 = L->is_proto3;
+    for (i = 0, count = pbL_count(info->oneof_decl); i < count; ++i) {
+        pb_OneofEntry *e = (pb_OneofEntry*)pb_settable(&t->oneof_index, i+1);
+        pbCM(e); pbCE(e->name = pb_newname(S, info->oneof_decl[i], NULL));
+        e->index = (int)i+1;
+    }
+    for (i = 0, count = pbL_count(info->field); i < count; ++i)
+        pbC(pbL_loadField(S, &info->field[i], L, t));
+    for (i = 0, count = pbL_count(info->extension); i < count; ++i)
+        pbC(pbL_loadField(S, &info->extension[i], L, NULL));
+    for (i = 0, count = pbL_count(info->enum_type); i < count; ++i)
+        pbC(pbL_loadEnum(S, &info->enum_type[i], L));
+    for (i = 0, count = pbL_count(info->nested_type); i < count; ++i)
+        pbC(pbL_loadType(S, &info->nested_type[i], L));
+    t->oneof_count = pbL_count(info->oneof_decl);
+    L->b.size = (unsigned)curr;
+    return PB_OK;
+}
+
+static int pbL_loadFile(pb_State *S, pbL_FileInfo *info, pb_Loader *L) {
+    size_t i, count, j, jcount, curr = 0;
+    pb_Name *syntax;
+    pbCM(syntax = pb_newname(S, pb_slice("proto3"), NULL));
+    for (i = 0, count = pbL_count(info); i < count; ++i) {
+        if (info[i].package.p)
+            pbC(pbL_prefixname(S, info[i].package, &curr, L, NULL));
+        L->is_proto3 = (pb_name(S, info[i].syntax, NULL) == syntax);
+        for (j = 0, jcount = pbL_count(info[i].enum_type); j < jcount; ++j)
+            pbC(pbL_loadEnum(S, &info[i].enum_type[j], L));
+        for (j = 0, jcount = pbL_count(info[i].message_type); j < jcount; ++j)
+            pbC(pbL_loadType(S, &info[i].message_type[j], L));
+        for (j = 0, jcount = pbL_count(info[i].extension); j < jcount; ++j)
+            pbC(pbL_loadField(S, &info[i].extension[j], L, NULL));
+        L->b.size = (unsigned)curr;
+    }
+    return PB_OK;
+}
+
+PB_API int pb_load(pb_State *S, pb_Slice *s) {
+    pbL_FileInfo *files = NULL;
+    pb_Loader L;
+    int r;
+    pb_initbuffer(&L.b);
+    L.s         = *s;
+    L.is_proto3 = 0;
+    if ((r = pbL_FileDescriptorSet(&L, &files)) == PB_OK)
+        r = pbL_loadFile(S, files, &L);
+    pbL_delFileInfo(files);
+    pb_resetbuffer(&L.b);
+    s->p = L.s.p;
+    return r;
+}
+
+
+PB_NS_END
+
+#endif /* PB_IMPLEMENTATION */
+
+/* cc: flags+='-shared -DPB_IMPLEMENTATION -xc' output='pb.so' */
+

+ 63 - 0
demo/protobuf/main.lua

@@ -0,0 +1,63 @@
+--- 模块功能:Google ProtoBuffs 编解码
+-- @module pb
+-- @author wendal
+-- @release 2022.9.8
+
+-- LuaTools需要PROJECT和VERSION这两个信息
+PROJECT = "pbdemo"
+VERSION = "1.0.1"
+
+log.info("main", PROJECT, VERSION)
+
+-- sys库是标配
+_G.sys = require("sys")
+
+--添加硬狗防止程序卡死
+if wdt then
+    wdt.init(15000)--初始化watchdog设置为15s
+    sys.timerLoopStart(wdt.feed, 10000)--10s喂一次狗
+end
+
+sys.taskInit(function()
+    sys.wait(500)
+    -- 如果没有这个库, 就云编译一份吧: https://wiki.luatos.com/develop/compile/Cloud_compilation.html
+    if not protobuf then
+        log.info("protobuf", "this demo need protobuf lib")
+        return
+    end
+    -- 加载 pb 文件, 这个是从pbtxt 转换得到的
+    -- 下载资源到模块时不需要下载pbtxt
+    -- 转换命令: protoc.exe -operson.pb --cpp_out=cpp person.pbtxt
+    -- protoc.exe 下载地址: https://github.com/protocolbuffers/protobuf/releases
+    protobuf.load(io.readFile("/luadb/person.pb"))
+    local tb = {
+        name = "wendal",
+        id = 123,
+        email = "abc@qq.com"
+    }
+    while 1 do
+        sys.wait(1000)
+        -- 用 protobuf 编码数据
+        local pbdata = protobuf.encode("Person", tb)
+        if pbdata then
+            -- 打印数据长度. 编码后的数据含不可见字符, toHex是方便显示
+            log.info("protobuf", "encode",  #pbdata, (pbdata:toHex()))
+        end
+        -- 用 json 编码数据, 用于对比大小
+        local jdata = json.encode(tb)
+        if jdata then
+            log.info("json", #jdata, jdata)
+        end
+        -- 可见 protobuffs 比 json 节省很多空间
+
+        -- 后续是演示解码
+        local re = protobuf.decode("Person", pbdata)
+        if re then
+            -- 打印数据, 因为table不能直接显示, 这里转成json来显示
+            log.info("protobuf", "decode", json.encode(re))
+        end
+    end
+end)
+
+-- 主循环, 必须加
+sys.run()

+ 7 - 0
demo/protobuf/person.pb

@@ -0,0 +1,7 @@
+
+R
+person.pbtxt"B
+Person
+name (	Rname
+id (Rid
+email (	Remail

+ 6 - 0
demo/protobuf/person.pbtxt

@@ -0,0 +1,6 @@
+syntax = "proto2";
+message Person {
+  optional string name = 1;
+  optional int32 id = 2;
+  optional string email = 3;
+}

+ 2 - 0
luat/include/luat_libs.h

@@ -130,4 +130,6 @@ LUAMOD_API int luaopen_ufont( lua_State *L );
 LUAMOD_API int luaopen_miniz( lua_State *L );
 LUAMOD_API int luaopen_mobile( lua_State *L );
 
+LUAMOD_API int luaopen_protobuf( lua_State *L );
+
 #endif