u_char *
ngx_stream_lua_copy_str_in_table(lua_State *L, int index, u_char *dst)
{
    double               key;
    int                  max;
    int                  i;
    int                  type;
    size_t               len;
    u_char              *p;

    if (index < 0) {
        index = lua_gettop(L) + index + 1;
    }

    max = 0;

    lua_pushnil(L); /* stack: table key */
    while (lua_next(L, index) != 0) { /* stack: table key value */
        key = lua_tonumber(L, -2);
        if (key > max) {
            max = (int) key;
        }

        lua_pop(L, 1); /* stack: table key */
    }

    for (i = 1; i <= max; i++) {
        lua_rawgeti(L, index, i); /* stack: table value */
        type = lua_type(L, -1);
        switch (type) {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            p = (u_char *) lua_tolstring(L, -1, &len);
            dst = ngx_copy(dst, p, len);
            break;

        case LUA_TNIL:
            *dst++ = 'n';
            *dst++ = 'i';
            *dst++ = 'l';
            break;

        case LUA_TBOOLEAN:
            if (lua_toboolean(L, -1)) {
                *dst++ = 't';
                *dst++ = 'r';
                *dst++ = 'u';
                *dst++ = 'e';

            } else {
                *dst++ = 'f';
                *dst++ = 'a';
                *dst++ = 'l';
                *dst++ = 's';
                *dst++ = 'e';
            }

            break;

        case LUA_TTABLE:
            dst = ngx_stream_lua_copy_str_in_table(L, -1, dst);
            break;

        case LUA_TLIGHTUSERDATA:

            *dst++ = 'n';
            *dst++ = 'u';
            *dst++ = 'l';
            *dst++ = 'l';
            break;

        default:
            luaL_error(L, "impossible to reach here");
            return NULL;
        }

        lua_pop(L, 1); /* stack: table */
    }

    return dst;
}
static int
ngx_stream_lua_ngx_echo(lua_State *L, unsigned newline)
{
    ngx_stream_session_t        *s;
    ngx_stream_lua_ctx_t        *ctx;
    const char                  *p;
    size_t                       len;
    size_t                       size;
    ngx_buf_t                   *b;
    ngx_chain_t                 *cl;
    ngx_int_t                    rc;
    int                          i;
    int                          nargs;
    int                          type;
    const char                  *msg;

    s = ngx_stream_lua_get_session(L);
    if (s == NULL) {
        return luaL_error(L, "no session object found");
    }

    ctx = ngx_stream_get_module_ctx(s, ngx_stream_lua_module);

    if (ctx == NULL) {
        return luaL_error(L, "no session ctx found");
    }

    ngx_stream_lua_check_context(L, ctx, NGX_STREAM_LUA_CONTEXT_CONTENT);

    if (ctx->acquired_raw_req_socket) {
        lua_pushnil(L);
        lua_pushliteral(L, "raw session socket acquired");
        return 2;
    }

    if (ctx->eof) {
        lua_pushnil(L);
        lua_pushliteral(L, "seen eof");
        return 2;
    }

    nargs = lua_gettop(L);
    size = 0;

    for (i = 1; i <= nargs; i++) {

        type = lua_type(L, i);

        switch (type) {
        case LUA_TNUMBER:
        case LUA_TSTRING:

            lua_tolstring(L, i, &len);
            size += len;
            break;

        case LUA_TNIL:

            size += sizeof("nil") - 1;
            break;

        case LUA_TBOOLEAN:

            if (lua_toboolean(L, i)) {
                size += sizeof("true") - 1;

            } else {
                size += sizeof("false") - 1;
            }

            break;

        case LUA_TTABLE:

            size += ngx_stream_lua_calc_strlen_in_table(L, i, i,
                    0 /* strict */);
            break;

        case LUA_TLIGHTUSERDATA:

            dd("userdata: %p", lua_touserdata(L, i));

            if (lua_touserdata(L, i) == NULL) {
                size += sizeof("null") - 1;
                break;
            }

            continue;

        default:

            msg = lua_pushfstring(L, "string, number, boolean, nil, "
                                  "ngx.null, or array table expected, "
                                  "but got %s", lua_typename(L, type));

            return luaL_argerror(L, i, msg);
        }
    }

    if (newline) {
        size += sizeof("\n") - 1;
    }

    if (size == 0) {
        /* do nothing for empty strings */
        lua_pushinteger(L, 1);
        return 1;
    }

    cl = ngx_stream_lua_chain_get_free_buf(s->connection->log,
                                           s->connection->pool,
                                           &ctx->free_bufs, size);

    if (cl == NULL) {
        return luaL_error(L, "no memory");
    }

    b = cl->buf;

    for (i = 1; i <= nargs; i++) {
        type = lua_type(L, i);
        switch (type) {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            p = lua_tolstring(L, i, &len);
            b->last = ngx_copy(b->last, (u_char *) p, len);
            break;

        case LUA_TNIL:
            *b->last++ = 'n';
            *b->last++ = 'i';
            *b->last++ = 'l';
            break;

        case LUA_TBOOLEAN:
            if (lua_toboolean(L, i)) {
                *b->last++ = 't';
                *b->last++ = 'r';
                *b->last++ = 'u';
                *b->last++ = 'e';

            } else {
                *b->last++ = 'f';
                *b->last++ = 'a';
                *b->last++ = 'l';
                *b->last++ = 's';
                *b->last++ = 'e';
            }

            break;

        case LUA_TTABLE:
            b->last = ngx_stream_lua_copy_str_in_table(L, i, b->last);
            break;

        case LUA_TLIGHTUSERDATA:
            *b->last++ = 'n';
            *b->last++ = 'u';
            *b->last++ = 'l';
            *b->last++ = 'l';
            break;

        default:
            return luaL_error(L, "impossible to reach here");
        }
    }

    if (newline) {
        *b->last++ = '\n';
    }

#if 0
    if (b->last != b->end) {
        return luaL_error(L, "buffer error: %p != %p", b->last, b->end);
    }
#endif

    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, s->connection->log, 0,
                   newline ? "stream lua say response"
                   : "stream lua print response");

    rc = ngx_stream_lua_send_chain_link(s, ctx, cl);

    if (rc == NGX_ERROR) {
        lua_pushnil(L);
        lua_pushliteral(L, "nginx output filter error");
        return 2;
    }

    dd("downstream write: %d, buf len: %d", (int) rc,
       (int) (b->last - b->pos));

    lua_pushinteger(L, 1);
    return 1;
}
static int
ngx_stream_lua_socket_udp_send(lua_State *L)
{
    ssize_t                               n;
    ngx_stream_session_t                 *s;
    u_char                               *p;
    size_t                                len;
    ngx_stream_lua_socket_udp_upstream_t *u;
    int                                   type;
    const char                           *msg;
    ngx_str_t                             query;
    ngx_stream_lua_srv_conf_t            *lscf;

    if (lua_gettop(L) != 2) {
        return luaL_error(L, "expecting 2 arguments (including the object), "
                          "but got %d", lua_gettop(L));
    }

    s = ngx_stream_lua_get_session(L);
    if (s == NULL) {
        return luaL_error(L, "session object not found");
    }

    luaL_checktype(L, 1, LUA_TTABLE);

    lua_rawgeti(L, 1, SOCKET_CTX_INDEX);
    u = lua_touserdata(L, -1);
    lua_pop(L, 1);

    if (u == NULL || u->udp_connection.connection == NULL) {
        lscf = ngx_stream_get_module_srv_conf(s, ngx_stream_lua_module);

        if (lscf->log_socket_errors) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                          "attempt to send data on a closed socket: u:%p, c:%p",
                          u, u ? u->udp_connection.connection : NULL);
        }

        lua_pushnil(L);
        lua_pushliteral(L, "closed");
        return 2;
    }

    if (u->session != s) {
        return luaL_error(L, "bad session");
    }

    if (u->ft_type) {
        u->ft_type = 0;
    }

    if (u->waiting) {
        lua_pushnil(L);
        lua_pushliteral(L, "socket busy");
        return 2;
    }

    type = lua_type(L, 2);
    switch (type) {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            lua_tolstring(L, 2, &len);
            break;

        case LUA_TTABLE:
            len = ngx_stream_lua_calc_strlen_in_table(L, 2, 2, 1 /* strict */);
            break;

        default:
            msg = lua_pushfstring(L, "string, number, boolean, nil, "
                                  "or array table expected, got %s",
                                  lua_typename(L, type));

            return luaL_argerror(L, 2, msg);
    }

    query.data = lua_newuserdata(L, len);
    query.len = len;

    switch (type) {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            p = (u_char *) lua_tolstring(L, 2, &len);
            ngx_memcpy(query.data, (u_char *) p, len);
            break;

        case LUA_TTABLE:
            (void) ngx_stream_lua_copy_str_in_table(L, 2, query.data);
            break;

        default:
            return luaL_error(L, "impossible to reach here");
    }

    u->ft_type = 0;

    /* mimic ngx_stream_upstream_init_session here */

#if 1
    u->waiting = 0;
#endif

    dd("sending query %.*s", (int) query.len, query.data);

    n = ngx_send(u->udp_connection.connection, query.data, query.len);

    dd("ngx_send returns %d (query len %d)", (int) n, (int) query.len);

    if (n == NGX_ERROR || n == NGX_AGAIN) {
        u->socket_errno = ngx_socket_errno;

        return ngx_stream_lua_socket_error_retval_handler(s, u, L);
    }

    if (n != (ssize_t) query.len) {
        dd("not the while query was sent");

        u->ft_type |= NGX_STREAM_LUA_SOCKET_FT_PARTIALWRITE;
        return ngx_stream_lua_socket_error_retval_handler(s, u, L);
    }

    dd("n == len");

    lua_pushinteger(L, 1);
    return 1;
}