1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * RTMP HTTP network protocol
3cabdff1aSopenharmony_ci * Copyright (c) 2012 Samuel Pitoiset
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * This file is part of FFmpeg.
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
8cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
9cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
10cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
11cabdff1aSopenharmony_ci *
12cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
13cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
14cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15cabdff1aSopenharmony_ci * Lesser General Public License for more details.
16cabdff1aSopenharmony_ci *
17cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
18cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
19cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20cabdff1aSopenharmony_ci */
21cabdff1aSopenharmony_ci
22cabdff1aSopenharmony_ci/**
23cabdff1aSopenharmony_ci * @file
24cabdff1aSopenharmony_ci * RTMP HTTP protocol
25cabdff1aSopenharmony_ci */
26cabdff1aSopenharmony_ci
27cabdff1aSopenharmony_ci#include "libavutil/avstring.h"
28cabdff1aSopenharmony_ci#include "libavutil/intfloat.h"
29cabdff1aSopenharmony_ci#include "libavutil/opt.h"
30cabdff1aSopenharmony_ci#include "libavutil/time.h"
31cabdff1aSopenharmony_ci#include "internal.h"
32cabdff1aSopenharmony_ci#include "http.h"
33cabdff1aSopenharmony_ci#include "rtmp.h"
34cabdff1aSopenharmony_ci
35cabdff1aSopenharmony_ci#define RTMPT_DEFAULT_PORT 80
36cabdff1aSopenharmony_ci#define RTMPTS_DEFAULT_PORT RTMPS_DEFAULT_PORT
37cabdff1aSopenharmony_ci
38cabdff1aSopenharmony_ci/* protocol handler context */
39cabdff1aSopenharmony_citypedef struct RTMP_HTTPContext {
40cabdff1aSopenharmony_ci    const AVClass *class;
41cabdff1aSopenharmony_ci    URLContext   *stream;           ///< HTTP stream
42cabdff1aSopenharmony_ci    char         host[256];         ///< hostname of the server
43cabdff1aSopenharmony_ci    int          port;              ///< port to connect (default is 80)
44cabdff1aSopenharmony_ci    char         client_id[64];     ///< client ID used for all requests except the first one
45cabdff1aSopenharmony_ci    int          seq;               ///< sequence ID used for all requests
46cabdff1aSopenharmony_ci    uint8_t      *out_data;         ///< output buffer
47cabdff1aSopenharmony_ci    int          out_size;          ///< current output buffer size
48cabdff1aSopenharmony_ci    int          out_capacity;      ///< current output buffer capacity
49cabdff1aSopenharmony_ci    int          initialized;       ///< flag indicating when the http context is initialized
50cabdff1aSopenharmony_ci    int          finishing;         ///< flag indicating when the client closes the connection
51cabdff1aSopenharmony_ci    int          nb_bytes_read;     ///< number of bytes read since the last request
52cabdff1aSopenharmony_ci    int          tls;               ///< use Transport Security Layer (RTMPTS)
53cabdff1aSopenharmony_ci} RTMP_HTTPContext;
54cabdff1aSopenharmony_ci
55cabdff1aSopenharmony_cistatic int rtmp_http_send_cmd(URLContext *h, const char *cmd)
56cabdff1aSopenharmony_ci{
57cabdff1aSopenharmony_ci    RTMP_HTTPContext *rt = h->priv_data;
58cabdff1aSopenharmony_ci    char uri[2048];
59cabdff1aSopenharmony_ci    uint8_t c;
60cabdff1aSopenharmony_ci    int ret;
61cabdff1aSopenharmony_ci
62cabdff1aSopenharmony_ci    ff_url_join(uri, sizeof(uri), "http", NULL, rt->host, rt->port,
63cabdff1aSopenharmony_ci                "/%s/%s/%d", cmd, rt->client_id, rt->seq++);
64cabdff1aSopenharmony_ci
65cabdff1aSopenharmony_ci    av_opt_set_bin(rt->stream->priv_data, "post_data", rt->out_data,
66cabdff1aSopenharmony_ci                   rt->out_size, 0);
67cabdff1aSopenharmony_ci
68cabdff1aSopenharmony_ci    /* send a new request to the server */
69cabdff1aSopenharmony_ci    if ((ret = ff_http_do_new_request(rt->stream, uri)) < 0)
70cabdff1aSopenharmony_ci        return ret;
71cabdff1aSopenharmony_ci
72cabdff1aSopenharmony_ci    /* re-init output buffer */
73cabdff1aSopenharmony_ci    rt->out_size = 0;
74cabdff1aSopenharmony_ci
75cabdff1aSopenharmony_ci    /* read the first byte which contains the polling interval */
76cabdff1aSopenharmony_ci    if ((ret = ffurl_read(rt->stream, &c, 1)) < 0)
77cabdff1aSopenharmony_ci        return ret;
78cabdff1aSopenharmony_ci
79cabdff1aSopenharmony_ci    /* re-init the number of bytes read */
80cabdff1aSopenharmony_ci    rt->nb_bytes_read = 0;
81cabdff1aSopenharmony_ci
82cabdff1aSopenharmony_ci    return ret;
83cabdff1aSopenharmony_ci}
84cabdff1aSopenharmony_ci
85cabdff1aSopenharmony_cistatic int rtmp_http_write(URLContext *h, const uint8_t *buf, int size)
86cabdff1aSopenharmony_ci{
87cabdff1aSopenharmony_ci    RTMP_HTTPContext *rt = h->priv_data;
88cabdff1aSopenharmony_ci
89cabdff1aSopenharmony_ci    if (rt->out_size + size > rt->out_capacity) {
90cabdff1aSopenharmony_ci        int err;
91cabdff1aSopenharmony_ci        rt->out_capacity = (rt->out_size + size) * 2;
92cabdff1aSopenharmony_ci        if ((err = av_reallocp(&rt->out_data, rt->out_capacity)) < 0) {
93cabdff1aSopenharmony_ci            rt->out_size = 0;
94cabdff1aSopenharmony_ci            rt->out_capacity = 0;
95cabdff1aSopenharmony_ci            return err;
96cabdff1aSopenharmony_ci        }
97cabdff1aSopenharmony_ci    }
98cabdff1aSopenharmony_ci
99cabdff1aSopenharmony_ci    memcpy(rt->out_data + rt->out_size, buf, size);
100cabdff1aSopenharmony_ci    rt->out_size += size;
101cabdff1aSopenharmony_ci
102cabdff1aSopenharmony_ci    return size;
103cabdff1aSopenharmony_ci}
104cabdff1aSopenharmony_ci
105cabdff1aSopenharmony_cistatic int rtmp_http_read(URLContext *h, uint8_t *buf, int size)
106cabdff1aSopenharmony_ci{
107cabdff1aSopenharmony_ci    RTMP_HTTPContext *rt = h->priv_data;
108cabdff1aSopenharmony_ci    int ret, off = 0;
109cabdff1aSopenharmony_ci
110cabdff1aSopenharmony_ci    /* try to read at least 1 byte of data */
111cabdff1aSopenharmony_ci    do {
112cabdff1aSopenharmony_ci        ret = ffurl_read(rt->stream, buf + off, size);
113cabdff1aSopenharmony_ci        if (ret < 0 && ret != AVERROR_EOF)
114cabdff1aSopenharmony_ci            return ret;
115cabdff1aSopenharmony_ci
116cabdff1aSopenharmony_ci        if (!ret || ret == AVERROR_EOF) {
117cabdff1aSopenharmony_ci            if (rt->finishing) {
118cabdff1aSopenharmony_ci                /* Do not send new requests when the client wants to
119cabdff1aSopenharmony_ci                 * close the connection. */
120cabdff1aSopenharmony_ci                return AVERROR(EAGAIN);
121cabdff1aSopenharmony_ci            }
122cabdff1aSopenharmony_ci
123cabdff1aSopenharmony_ci            /* When the client has reached end of file for the last request,
124cabdff1aSopenharmony_ci             * we have to send a new request if we have buffered data.
125cabdff1aSopenharmony_ci             * Otherwise, we have to send an idle POST. */
126cabdff1aSopenharmony_ci            if (rt->out_size > 0) {
127cabdff1aSopenharmony_ci                if ((ret = rtmp_http_send_cmd(h, "send")) < 0)
128cabdff1aSopenharmony_ci                    return ret;
129cabdff1aSopenharmony_ci            } else {
130cabdff1aSopenharmony_ci                if (rt->nb_bytes_read == 0) {
131cabdff1aSopenharmony_ci                    /* Wait 50ms before retrying to read a server reply in
132cabdff1aSopenharmony_ci                     * order to reduce the number of idle requests. */
133cabdff1aSopenharmony_ci                    av_usleep(50000);
134cabdff1aSopenharmony_ci                }
135cabdff1aSopenharmony_ci
136cabdff1aSopenharmony_ci                if ((ret = rtmp_http_write(h, "", 1)) < 0)
137cabdff1aSopenharmony_ci                    return ret;
138cabdff1aSopenharmony_ci
139cabdff1aSopenharmony_ci                if ((ret = rtmp_http_send_cmd(h, "idle")) < 0)
140cabdff1aSopenharmony_ci                    return ret;
141cabdff1aSopenharmony_ci            }
142cabdff1aSopenharmony_ci
143cabdff1aSopenharmony_ci            if (h->flags & AVIO_FLAG_NONBLOCK) {
144cabdff1aSopenharmony_ci                /* no incoming data to handle in nonblocking mode */
145cabdff1aSopenharmony_ci                return AVERROR(EAGAIN);
146cabdff1aSopenharmony_ci            }
147cabdff1aSopenharmony_ci        } else {
148cabdff1aSopenharmony_ci            off  += ret;
149cabdff1aSopenharmony_ci            size -= ret;
150cabdff1aSopenharmony_ci            rt->nb_bytes_read += ret;
151cabdff1aSopenharmony_ci        }
152cabdff1aSopenharmony_ci    } while (off <= 0);
153cabdff1aSopenharmony_ci
154cabdff1aSopenharmony_ci    return off;
155cabdff1aSopenharmony_ci}
156cabdff1aSopenharmony_ci
157cabdff1aSopenharmony_cistatic int rtmp_http_close(URLContext *h)
158cabdff1aSopenharmony_ci{
159cabdff1aSopenharmony_ci    RTMP_HTTPContext *rt = h->priv_data;
160cabdff1aSopenharmony_ci    uint8_t tmp_buf[2048];
161cabdff1aSopenharmony_ci    int ret = 0;
162cabdff1aSopenharmony_ci
163cabdff1aSopenharmony_ci    if (rt->initialized) {
164cabdff1aSopenharmony_ci        /* client wants to close the connection */
165cabdff1aSopenharmony_ci        rt->finishing = 1;
166cabdff1aSopenharmony_ci
167cabdff1aSopenharmony_ci        do {
168cabdff1aSopenharmony_ci            ret = rtmp_http_read(h, tmp_buf, sizeof(tmp_buf));
169cabdff1aSopenharmony_ci        } while (ret > 0);
170cabdff1aSopenharmony_ci
171cabdff1aSopenharmony_ci        /* re-init output buffer before sending the close command */
172cabdff1aSopenharmony_ci        rt->out_size = 0;
173cabdff1aSopenharmony_ci
174cabdff1aSopenharmony_ci        if ((ret = rtmp_http_write(h, "", 1)) == 1)
175cabdff1aSopenharmony_ci            ret = rtmp_http_send_cmd(h, "close");
176cabdff1aSopenharmony_ci    }
177cabdff1aSopenharmony_ci
178cabdff1aSopenharmony_ci    av_freep(&rt->out_data);
179cabdff1aSopenharmony_ci    ffurl_closep(&rt->stream);
180cabdff1aSopenharmony_ci
181cabdff1aSopenharmony_ci    return ret;
182cabdff1aSopenharmony_ci}
183cabdff1aSopenharmony_ci
184cabdff1aSopenharmony_cistatic int rtmp_http_open(URLContext *h, const char *uri, int flags)
185cabdff1aSopenharmony_ci{
186cabdff1aSopenharmony_ci    RTMP_HTTPContext *rt = h->priv_data;
187cabdff1aSopenharmony_ci    char headers[1024], url[1024];
188cabdff1aSopenharmony_ci    int ret, off = 0;
189cabdff1aSopenharmony_ci
190cabdff1aSopenharmony_ci    av_url_split(NULL, 0, NULL, 0, rt->host, sizeof(rt->host), &rt->port,
191cabdff1aSopenharmony_ci                 NULL, 0, uri);
192cabdff1aSopenharmony_ci
193cabdff1aSopenharmony_ci    /* This is the first request that is sent to the server in order to
194cabdff1aSopenharmony_ci     * register a client on the server and start a new session. The server
195cabdff1aSopenharmony_ci     * replies with a unique id (usually a number) that is used by the client
196cabdff1aSopenharmony_ci     * for all future requests.
197cabdff1aSopenharmony_ci     * Note: the reply doesn't contain a value for the polling interval.
198cabdff1aSopenharmony_ci     * A successful connect resets the consecutive index that is used
199cabdff1aSopenharmony_ci     * in the URLs. */
200cabdff1aSopenharmony_ci    if (rt->tls) {
201cabdff1aSopenharmony_ci        if (rt->port < 0)
202cabdff1aSopenharmony_ci            rt->port = RTMPTS_DEFAULT_PORT;
203cabdff1aSopenharmony_ci        ff_url_join(url, sizeof(url), "https", NULL, rt->host, rt->port, "/open/1");
204cabdff1aSopenharmony_ci    } else {
205cabdff1aSopenharmony_ci        if (rt->port < 0)
206cabdff1aSopenharmony_ci            rt->port = RTMPT_DEFAULT_PORT;
207cabdff1aSopenharmony_ci        ff_url_join(url, sizeof(url), "http", NULL, rt->host, rt->port, "/open/1");
208cabdff1aSopenharmony_ci    }
209cabdff1aSopenharmony_ci
210cabdff1aSopenharmony_ci    /* alloc the http context */
211cabdff1aSopenharmony_ci    if ((ret = ffurl_alloc(&rt->stream, url, AVIO_FLAG_READ_WRITE, &h->interrupt_callback)) < 0)
212cabdff1aSopenharmony_ci        goto fail;
213cabdff1aSopenharmony_ci
214cabdff1aSopenharmony_ci    /* set options */
215cabdff1aSopenharmony_ci    snprintf(headers, sizeof(headers),
216cabdff1aSopenharmony_ci             "Cache-Control: no-cache\r\n"
217cabdff1aSopenharmony_ci             "Content-type: application/x-fcs\r\n"
218cabdff1aSopenharmony_ci             "User-Agent: Shockwave Flash\r\n");
219cabdff1aSopenharmony_ci    av_opt_set(rt->stream->priv_data, "headers", headers, 0);
220cabdff1aSopenharmony_ci    av_opt_set(rt->stream->priv_data, "multiple_requests", "1", 0);
221cabdff1aSopenharmony_ci    av_opt_set_bin(rt->stream->priv_data, "post_data", "", 1, 0);
222cabdff1aSopenharmony_ci
223cabdff1aSopenharmony_ci    if (!rt->stream->protocol_whitelist && h->protocol_whitelist) {
224cabdff1aSopenharmony_ci        rt->stream->protocol_whitelist = av_strdup(h->protocol_whitelist);
225cabdff1aSopenharmony_ci        if (!rt->stream->protocol_whitelist) {
226cabdff1aSopenharmony_ci            ret = AVERROR(ENOMEM);
227cabdff1aSopenharmony_ci            goto fail;
228cabdff1aSopenharmony_ci        }
229cabdff1aSopenharmony_ci    }
230cabdff1aSopenharmony_ci
231cabdff1aSopenharmony_ci    /* open the http context */
232cabdff1aSopenharmony_ci    if ((ret = ffurl_connect(rt->stream, NULL)) < 0)
233cabdff1aSopenharmony_ci        goto fail;
234cabdff1aSopenharmony_ci
235cabdff1aSopenharmony_ci    /* read the server reply which contains a unique ID */
236cabdff1aSopenharmony_ci    for (;;) {
237cabdff1aSopenharmony_ci        ret = ffurl_read(rt->stream, rt->client_id + off, sizeof(rt->client_id) - off);
238cabdff1aSopenharmony_ci        if (!ret || ret == AVERROR_EOF)
239cabdff1aSopenharmony_ci            break;
240cabdff1aSopenharmony_ci        if (ret < 0)
241cabdff1aSopenharmony_ci            goto fail;
242cabdff1aSopenharmony_ci        off += ret;
243cabdff1aSopenharmony_ci        if (off == sizeof(rt->client_id)) {
244cabdff1aSopenharmony_ci            ret = AVERROR(EIO);
245cabdff1aSopenharmony_ci            goto fail;
246cabdff1aSopenharmony_ci        }
247cabdff1aSopenharmony_ci    }
248cabdff1aSopenharmony_ci    while (off > 0 && av_isspace(rt->client_id[off - 1]))
249cabdff1aSopenharmony_ci        off--;
250cabdff1aSopenharmony_ci    rt->client_id[off] = '\0';
251cabdff1aSopenharmony_ci
252cabdff1aSopenharmony_ci    /* http context is now initialized */
253cabdff1aSopenharmony_ci    rt->initialized = 1;
254cabdff1aSopenharmony_ci    return 0;
255cabdff1aSopenharmony_ci
256cabdff1aSopenharmony_cifail:
257cabdff1aSopenharmony_ci    rtmp_http_close(h);
258cabdff1aSopenharmony_ci    return ret;
259cabdff1aSopenharmony_ci}
260cabdff1aSopenharmony_ci
261cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(RTMP_HTTPContext, x)
262cabdff1aSopenharmony_ci#define DEC AV_OPT_FLAG_DECODING_PARAM
263cabdff1aSopenharmony_ci
264cabdff1aSopenharmony_cistatic const AVOption ffrtmphttp_options[] = {
265cabdff1aSopenharmony_ci    {"ffrtmphttp_tls", "Use a HTTPS tunneling connection (RTMPTS).", OFFSET(tls), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC},
266cabdff1aSopenharmony_ci    { NULL },
267cabdff1aSopenharmony_ci};
268cabdff1aSopenharmony_ci
269cabdff1aSopenharmony_cistatic const AVClass ffrtmphttp_class = {
270cabdff1aSopenharmony_ci    .class_name = "ffrtmphttp",
271cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
272cabdff1aSopenharmony_ci    .option     = ffrtmphttp_options,
273cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT,
274cabdff1aSopenharmony_ci};
275cabdff1aSopenharmony_ci
276cabdff1aSopenharmony_ciconst URLProtocol ff_ffrtmphttp_protocol = {
277cabdff1aSopenharmony_ci    .name           = "ffrtmphttp",
278cabdff1aSopenharmony_ci    .url_open       = rtmp_http_open,
279cabdff1aSopenharmony_ci    .url_read       = rtmp_http_read,
280cabdff1aSopenharmony_ci    .url_write      = rtmp_http_write,
281cabdff1aSopenharmony_ci    .url_close      = rtmp_http_close,
282cabdff1aSopenharmony_ci    .priv_data_size = sizeof(RTMP_HTTPContext),
283cabdff1aSopenharmony_ci    .flags          = URL_PROTOCOL_FLAG_NETWORK,
284cabdff1aSopenharmony_ci    .priv_data_class= &ffrtmphttp_class,
285cabdff1aSopenharmony_ci    .default_whitelist = "https,http,tcp,tls",
286cabdff1aSopenharmony_ci};
287