static char *
ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                          *value, v, n;
    ngx_rtmp_relay_app_conf_t          *racf;
    ngx_rtmp_relay_target_t            *target, **t;
    void                               *addrs;
    ngx_url_t                          *u;
    ngx_uint_t                          i;
    ngx_int_t                           is_pull, is_static;
    ngx_event_t                       **ee, *e;
    ngx_rtmp_relay_static_t            *rs;
    u_char                             *p;

    value = cf->args->elts;

    racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module);

    is_pull = (value[0].data[3] == 'l');
    is_static = 0;

    target = ngx_pcalloc(cf->pool, sizeof(*target));
    if (target == NULL) {
        return NGX_CONF_ERROR;
    }

    target->tag = &ngx_rtmp_relay_module;
    target->data = target;

    u = &target->url;
    u->default_port = 1935;
    u->uri_part = 1;
    u->url = value[1];

    if (ngx_strncasecmp(u->url.data, (u_char *) "rtmp://", 7) == 0) {
        u->url.data += 7;
        u->url.len  -= 7;
    }

    if (ngx_parse_url(cf->pool, u) != NGX_OK) {
        if (u->err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                    "%s in url \"%V\"", u->err, &u->url);
        }
        return NGX_CONF_ERROR;
    }

    value += 2;
    for (i = 2; i < cf->args->nelts; ++i, ++value) {
        p = ngx_strlchr(value->data, value->data + value->len, '=');

        if (p == NULL) {
            n = *value;
            ngx_str_set(&v, "1");

        } else {
            n.data = value->data;
            n.len  = p - value->data;

            v.data = p + 1;
            v.len  = value->data + value->len - p - 1;
        }

#define NGX_RTMP_RELAY_STR_PAR(name, var)                                     \
        if (n.len == sizeof(name) - 1                                         \
            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \
        {                                                                     \
            target->var = v;                                                  \
            continue;                                                         \
        }

#define NGX_RTMP_RELAY_NUM_PAR(name, var)                                     \
        if (n.len == sizeof(name) - 1                                         \
            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \
        {                                                                     \
            target->var = ngx_atoi(v.data, v.len);                            \
            continue;                                                         \
        }

        NGX_RTMP_RELAY_STR_PAR("app",         app);
        NGX_RTMP_RELAY_STR_PAR("name",        name);
        NGX_RTMP_RELAY_STR_PAR("tcUrl",       tc_url);
        NGX_RTMP_RELAY_STR_PAR("pageUrl",     page_url);
        NGX_RTMP_RELAY_STR_PAR("swfUrl",      swf_url);
        NGX_RTMP_RELAY_STR_PAR("flashVer",    flash_ver);
        NGX_RTMP_RELAY_STR_PAR("playPath",    play_path);
        NGX_RTMP_RELAY_NUM_PAR("live",        live);
        NGX_RTMP_RELAY_NUM_PAR("start",       start);
        NGX_RTMP_RELAY_NUM_PAR("stop",        stop);

#undef NGX_RTMP_RELAY_STR_PAR
#undef NGX_RTMP_RELAY_NUM_PAR

        if (n.len == sizeof("static") - 1 &&
            ngx_strncasecmp(n.data, (u_char *) "static", n.len) == 0 &&
            ngx_atoi(v.data, v.len))
        {
            is_static = 1;
            continue;
        }

        return "unsuppored parameter";
    }

    if (is_static) {

        if (!is_pull) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "static push is not allowed");
            return NGX_CONF_ERROR;
        }

        if (target->name.len == 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "stream name missing in static pull "
                               "declaration");
            return NGX_CONF_ERROR;
        }

        t = racf->static_pulls.elts;
        for (i = 0; i < racf->static_pulls.nelts; i++) {

            if (t[i]->name.len == target->name.len
                && ngx_strncmp(t[i]->name.data, target->name.data, target->name.len) == 0)
            {
                addrs = ngx_palloc(cf->pool, (u->naddrs + t[i]->url.naddrs) * sizeof(ngx_addr_t));
                if (addrs == NULL) {
                    return NGX_CONF_ERROR;
                }

                ngx_memcpy(addrs, t[i]->url.addrs, t[i]->url.naddrs * sizeof(ngx_addr_t));
                ngx_memcpy((char *)addrs + t[i]->url.naddrs * sizeof(ngx_addr_t), u->addrs, u->naddrs * sizeof(ngx_addr_t));

                t[i]->url.naddrs += u->naddrs;
                t[i]->url.addrs = addrs;

                return NGX_CONF_OK;
            }
        }

        ee = ngx_array_push(&racf->static_events);
        if (ee == NULL) {
            return NGX_CONF_ERROR;
        }

        e = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));
        if (e == NULL) {
            return NGX_CONF_ERROR;
        }

        *ee = e;

        rs = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_static_t));
        if (rs == NULL) {
            return NGX_CONF_ERROR;
        }

        rs->target = target;

        e->data = rs;
        e->log = &cf->cycle->new_log;
        e->handler = ngx_rtmp_relay_static_pull_reconnect;

        t = ngx_array_push(&racf->static_pulls);

    } else if (is_pull) {
        t = ngx_array_push(&racf->pulls);

    } else {
        t = ngx_array_push(&racf->pushes);
    }

    if (t == NULL) {
        return NGX_CONF_ERROR;
    }

    *t = target;

    return NGX_CONF_OK;
}
static char *
ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_str_t                          *value, v, n;
    ngx_rtmp_relay_app_conf_t          *racf;
    ngx_rtmp_relay_target_t            *target, **t;
    ngx_url_t                          *u;
    ngx_uint_t                          i;
    u_char                             *p;

    value = cf->args->elts;

    racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module);

    t = ngx_array_push(value[0].data[3] == 'h' 
            ? &racf->pushes /* push */
            : &racf->pulls  /* pull */
        );
    
    if (t == NULL) {
        return NGX_CONF_ERROR;
    }

    target = ngx_pcalloc(cf->pool, sizeof(*target));
    if (target == NULL) {
        return NGX_CONF_ERROR;
    }

    *t = target;

    target->tag = &ngx_rtmp_relay_module;
    target->data = target;

    u = &target->url;
    u->default_port = 1935;
    u->uri_part = 1;
    u->url = value[1];

    if (ngx_strncasecmp(u->url.data, (u_char *) "rtmp://", 7) == 0) {
        u->url.data += 7;
        u->url.len  -= 7;
    }
    
    if (ngx_parse_url(cf->pool, u) != NGX_OK) {
        if (u->err) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                    "%s in url \"%V\"", u->err, &u->url);
        }
        return NGX_CONF_ERROR;
    }

    value += 2;
    for (i = 2; i < cf->args->nelts; ++i, ++value) {
        p = ngx_strlchr(value->data, value->data + value->len, '=');
        if (p == NULL) {
            return "key=value expected";
        }

        if (p == value->data + value->len - 1) {
            continue;
        }

        n.data = value->data;
        n.len  = p - value->data;

        v.data = p + 1;
        v.len  = value->data + value->len - p - 1;

#define NGX_RTMP_RELAY_STR_PAR(name, var)                                     \
        if (n.len == sizeof(#name) - 1                                        \
            && ngx_strncasecmp(n.data, (u_char *)#name, n.len) == 0)          \
        {                                                                     \
            target->var = v;                                                  \
            continue;                                                         \
        }

#define NGX_RTMP_RELAY_NUM_PAR(name, var)                                     \
        if (n.len == sizeof(#name) - 1                                        \
            && ngx_strncasecmp(n.data, (u_char *)#name, n.len) == 0)          \
        {                                                                     \
            target->var = ngx_atoi(v.data, v.len);                            \
            continue;                                                         \
        }

        NGX_RTMP_RELAY_STR_PAR(app,         app);
        NGX_RTMP_RELAY_STR_PAR(name,        name);
        NGX_RTMP_RELAY_STR_PAR(tcUrl,       tc_url);
        NGX_RTMP_RELAY_STR_PAR(pageUrl,     page_url);
        NGX_RTMP_RELAY_STR_PAR(swfUrl,      swf_url);
        NGX_RTMP_RELAY_STR_PAR(flashVer,    flash_ver);
        NGX_RTMP_RELAY_STR_PAR(playPath,    play_path);
        NGX_RTMP_RELAY_NUM_PAR(live,        live);
        NGX_RTMP_RELAY_NUM_PAR(start,       start);
        NGX_RTMP_RELAY_NUM_PAR(stop,        stop);

#undef NGX_RTMP_RELAY_STR_PAR
#undef NGX_RTMP_RELAY_NUM_PAR

        return "unsuppored parameter";
    }

    return NGX_CONF_OK;
}
ngx_int_t
ngx_rtmp_parse_relay_str(ngx_pool_t *pool, ngx_rtmp_relay_target_t *target,
    u_char *is_static)
{
    ngx_str_t                     parts, v, n;
    u_char                       *b, *m, *e, *last, *dst, *src;

    parts.len = target->url.uri.len;
    parts.data = ngx_pstrdup(pool, &target->url.uri);
    if (parts.data == NULL) {
        return NGX_ERROR;
    }

    last = parts.data + parts.len - 1;
    b = ngx_strlchr(parts.data, last + 1, '?');

    while (b != NULL && ++b < last) {
        e = ngx_strlchr(b + 1, last + 1, '&');

        if (e == NULL) {
            e = last;

        } else if (e == b + 1) {
            b = e + 1;
            continue;

        } else {
            *e = '\0';
            e--;
        }

        m = ngx_strlchr(b, e + 1, '=');

        if (m == NULL) {
            n.data = b;
            n.len  = e - b + 1;
            ngx_str_set(&v, "1");

        } else if (m == e) {
            n.data = b;
            n.len  = e - b;
            ngx_str_set(&v, "1");

        } else if (m == b) {
            b = e + 1;
            continue;

        } else {
            n.data = b;
            n.len  = m - b;

            v.data = m + 1;
            v.len  = e - m;
        }

#define NGX_RTMP_RELAY_STR_PAR(name, var)                                     \
        if (n.len == sizeof(name) - 1                                         \
            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \
        {                                                                     \
            src = v.data;                                                     \
            dst = ngx_pnalloc(pool, v.len);                                   \
            if (dst == NULL) {                                                \
                return NGX_ERROR;                                             \
            }                                                                 \
            target->var.data = dst;                                           \
            ngx_unescape_uri(&dst, &src, v.len, 0);                           \
            target->var.len = dst - target->var.data;                         \
            b = e + 1;                                                        \
            continue;                                                         \
        }

#define NGX_RTMP_RELAY_NUM_PAR(name, var)                                     \
        if (n.len == sizeof(name) - 1                                         \
            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \
        {                                                                     \
            target->var = ngx_atoi(v.data, v.len);                            \
            b = e + 1;                                                        \
            continue;                                                         \
        }

        NGX_RTMP_RELAY_STR_PAR("app",         app);
        NGX_RTMP_RELAY_STR_PAR("name",        name);
        NGX_RTMP_RELAY_STR_PAR("tcUrl",       tc_url);
        NGX_RTMP_RELAY_STR_PAR("pageUrl",     page_url);
        NGX_RTMP_RELAY_STR_PAR("swfUrl",      swf_url);
        NGX_RTMP_RELAY_STR_PAR("flashVer",    flash_ver);
        NGX_RTMP_RELAY_STR_PAR("playPath",    play_path);
        NGX_RTMP_RELAY_NUM_PAR("live",        live);
        NGX_RTMP_RELAY_NUM_PAR("start",       start);
        NGX_RTMP_RELAY_NUM_PAR("stop",        stop);

#undef NGX_RTMP_RELAY_STR_PAR
#undef NGX_RTMP_RELAY_NUM_PAR

        if (is_static && n.len == sizeof("static") - 1 &&
            ngx_strncasecmp(n.data, (u_char *) "static", n.len) == 0 &&
            ngx_atoi(v.data, v.len))
        {
            *is_static = 1;
            continue;
        }

        return NGX_ERROR;
    }

    return NGX_OK;
}