1/*
2 * MMS protocol over HTTP
3 * Copyright (c) 2010 Zhentan Feng <spyfeng at gmail dot com>
4 *
5 * This file is part of Libav.
6 *
7 * Libav is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Libav is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with Libav; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22/*
23 * Reference
24 * Windows Media HTTP Streaming Protocol.
25 * http://msdn.microsoft.com/en-us/library/cc251059(PROT.10).aspx
26 */
27
28#include <string.h>
29#include "libavutil/intreadwrite.h"
30#include "libavutil/avstring.h"
31#include "libavutil/opt.h"
32#include "internal.h"
33#include "mms.h"
34#include "asf.h"
35#include "http.h"
36#include "url.h"
37
38#define CHUNK_HEADER_LENGTH 4   // 2bytes chunk type and 2bytes chunk length.
39#define EXT_HEADER_LENGTH   8   // 4bytes sequence, 2bytes useless and 2bytes chunk length.
40
41// see Ref 2.2.1.8
42#define USERAGENT  "User-Agent: NSPlayer/4.1.0.3856\r\n"
43// see Ref 2.2.1.4.33
44// the guid value can be changed to any valid value.
45#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n"
46
47// see Ref 2.2.3 for packet type define:
48// chunk type contains 2 fields: Frame and PacketID.
49// Frame is 0x24 or 0xA4(rarely), different PacketID indicates different packet type.
50typedef enum {
51    CHUNK_TYPE_DATA          = 0x4424,
52    CHUNK_TYPE_ASF_HEADER    = 0x4824,
53    CHUNK_TYPE_END           = 0x4524,
54    CHUNK_TYPE_STREAM_CHANGE = 0x4324,
55} ChunkType;
56
57typedef struct {
58    MMSContext mms;
59    int request_seq;  ///< request packet sequence
60    int chunk_seq;    ///< data packet sequence
61} MMSHContext;
62
63static int mmsh_close(URLContext *h)
64{
65    MMSHContext *mmsh = (MMSHContext *)h->priv_data;
66    MMSContext *mms   = &mmsh->mms;
67    if (mms->mms_hd)
68        ffurl_close(mms->mms_hd);
69    av_free(mms->streams);
70    av_free(mms->asf_header);
71    return 0;
72}
73
74static ChunkType get_chunk_header(MMSHContext *mmsh, int *len)
75{
76    MMSContext *mms = &mmsh->mms;
77    uint8_t chunk_header[CHUNK_HEADER_LENGTH];
78    uint8_t ext_header[EXT_HEADER_LENGTH];
79    ChunkType chunk_type;
80    int chunk_len, res, ext_header_len;
81
82    res = ffurl_read_complete(mms->mms_hd, chunk_header, CHUNK_HEADER_LENGTH);
83    if (res != CHUNK_HEADER_LENGTH) {
84        av_log(NULL, AV_LOG_ERROR, "Read data packet header failed!\n");
85        return AVERROR(EIO);
86    }
87    chunk_type = AV_RL16(chunk_header);
88    chunk_len  = AV_RL16(chunk_header + 2);
89
90    switch (chunk_type) {
91    case CHUNK_TYPE_END:
92    case CHUNK_TYPE_STREAM_CHANGE:
93        ext_header_len = 4;
94        break;
95    case CHUNK_TYPE_ASF_HEADER:
96    case CHUNK_TYPE_DATA:
97        ext_header_len = 8;
98        break;
99    default:
100        av_log(NULL, AV_LOG_ERROR, "Strange chunk type %d\n", chunk_type);
101        return AVERROR_INVALIDDATA;
102    }
103
104    res = ffurl_read_complete(mms->mms_hd, ext_header, ext_header_len);
105    if (res != ext_header_len) {
106        av_log(NULL, AV_LOG_ERROR, "Read ext header failed!\n");
107        return AVERROR(EIO);
108    }
109    *len = chunk_len - ext_header_len;
110    if (chunk_type == CHUNK_TYPE_END || chunk_type == CHUNK_TYPE_DATA)
111        mmsh->chunk_seq = AV_RL32(ext_header);
112    return chunk_type;
113}
114
115static int read_data_packet(MMSHContext *mmsh, const int len)
116{
117    MMSContext *mms   = &mmsh->mms;
118    int res;
119    if (len > sizeof(mms->in_buffer)) {
120        av_log(NULL, AV_LOG_ERROR,
121               "Data packet length %d exceeds the in_buffer size %zu\n",
122               len, sizeof(mms->in_buffer));
123        return AVERROR(EIO);
124    }
125    res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len);
126    av_dlog(NULL, "Data packet len = %d\n", len);
127    if (res != len) {
128        av_log(NULL, AV_LOG_ERROR, "Read data packet failed!\n");
129        return AVERROR(EIO);
130    }
131    if (len > mms->asf_packet_len) {
132        av_log(NULL, AV_LOG_ERROR,
133               "Chunk length %d exceed packet length %d\n",len, mms->asf_packet_len);
134        return AVERROR_INVALIDDATA;
135    } else {
136        memset(mms->in_buffer + len, 0, mms->asf_packet_len - len); // padding
137    }
138    mms->read_in_ptr      = mms->in_buffer;
139    mms->remaining_in_len = mms->asf_packet_len;
140    return 0;
141}
142
143static int get_http_header_data(MMSHContext *mmsh)
144{
145    MMSContext *mms = &mmsh->mms;
146    int res, len;
147    ChunkType chunk_type;
148
149    for (;;) {
150        len = 0;
151        res = chunk_type = get_chunk_header(mmsh, &len);
152        if (res < 0) {
153            return res;
154        } else if (chunk_type == CHUNK_TYPE_ASF_HEADER){
155            // get asf header and stored it
156            if (!mms->header_parsed) {
157                if (mms->asf_header) {
158                    if (len != mms->asf_header_size) {
159                        mms->asf_header_size = len;
160                        av_dlog(NULL, "Header len changed from %d to %d\n",
161                                mms->asf_header_size, len);
162                        av_freep(&mms->asf_header);
163                    }
164                }
165                mms->asf_header = av_mallocz(len);
166                if (!mms->asf_header) {
167                    return AVERROR(ENOMEM);
168                }
169                mms->asf_header_size = len;
170            }
171            if (len > mms->asf_header_size) {
172                av_log(NULL, AV_LOG_ERROR,
173                       "Asf header packet len = %d exceed the asf header buf size %d\n",
174                       len, mms->asf_header_size);
175                return AVERROR(EIO);
176            }
177            res = ffurl_read_complete(mms->mms_hd, mms->asf_header, len);
178            if (res != len) {
179                av_log(NULL, AV_LOG_ERROR,
180                       "Recv asf header data len %d != expected len %d\n", res, len);
181                return AVERROR(EIO);
182            }
183            mms->asf_header_size = len;
184            if (!mms->header_parsed) {
185                res = ff_mms_asf_header_parser(mms);
186                mms->header_parsed = 1;
187                return res;
188            }
189        } else if (chunk_type == CHUNK_TYPE_DATA) {
190            // read data packet and do padding
191            return read_data_packet(mmsh, len);
192        } else {
193            if (len) {
194                if (len > sizeof(mms->in_buffer)) {
195                    av_log(NULL, AV_LOG_ERROR,
196                           "Other packet len = %d exceed the in_buffer size %zu\n",
197                           len, sizeof(mms->in_buffer));
198                    return AVERROR(EIO);
199                }
200                res = ffurl_read_complete(mms->mms_hd, mms->in_buffer, len);
201                if (res != len) {
202                    av_log(NULL, AV_LOG_ERROR, "Read other chunk type data failed!\n");
203                    return AVERROR(EIO);
204                } else {
205                    av_dlog(NULL, "Skip chunk type %d \n", chunk_type);
206                    continue;
207                }
208            }
209        }
210    }
211}
212
213static int mmsh_open(URLContext *h, const char *uri, int flags)
214{
215    int i, port, err;
216    char httpname[256], path[256], host[128], location[1024];
217    char *stream_selection = NULL;
218    char headers[1024];
219    MMSHContext *mmsh = h->priv_data;
220    MMSContext *mms;
221
222    mmsh->request_seq = h->is_streamed = 1;
223    mms = &mmsh->mms;
224    av_strlcpy(location, uri, sizeof(location));
225
226    av_url_split(NULL, 0, NULL, 0,
227        host, sizeof(host), &port, path, sizeof(path), location);
228    if (port<0)
229        port = 80; // default mmsh protocol port
230    ff_url_join(httpname, sizeof(httpname), "http", NULL, host, port, "%s", path);
231
232    if (ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ,
233                    &h->interrupt_callback) < 0) {
234        return AVERROR(EIO);
235    }
236
237    snprintf(headers, sizeof(headers),
238             "Accept: */*\r\n"
239             USERAGENT
240             "Host: %s:%d\r\n"
241             "Pragma: no-cache,rate=1.000000,stream-time=0,"
242             "stream-offset=0:0,request-context=%u,max-duration=0\r\n"
243             CLIENTGUID
244             "Connection: Close\r\n\r\n",
245             host, port, mmsh->request_seq++);
246    av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0);
247
248    err = ffurl_connect(mms->mms_hd, NULL);
249    if (err) {
250        goto fail;
251    }
252    err = get_http_header_data(mmsh);
253    if (err) {
254        av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
255        goto fail;
256    }
257
258    // close the socket and then reopen it for sending the second play request.
259    ffurl_close(mms->mms_hd);
260    memset(headers, 0, sizeof(headers));
261    if ((err = ffurl_alloc(&mms->mms_hd, httpname, AVIO_FLAG_READ,
262                           &h->interrupt_callback)) < 0) {
263        goto fail;
264    }
265    stream_selection = av_mallocz(mms->stream_num * 19 + 1);
266    if (!stream_selection)
267        return AVERROR(ENOMEM);
268    for (i = 0; i < mms->stream_num; i++) {
269        char tmp[20];
270        err = snprintf(tmp, sizeof(tmp), "ffff:%d:0 ", mms->streams[i].id);
271        if (err < 0)
272            goto fail;
273        av_strlcat(stream_selection, tmp, mms->stream_num * 19 + 1);
274    }
275    // send play request
276    err = snprintf(headers, sizeof(headers),
277                   "Accept: */*\r\n"
278                   USERAGENT
279                   "Host: %s:%d\r\n"
280                   "Pragma: no-cache,rate=1.000000,request-context=%u\r\n"
281                   "Pragma: xPlayStrm=1\r\n"
282                   CLIENTGUID
283                   "Pragma: stream-switch-count=%d\r\n"
284                   "Pragma: stream-switch-entry=%s\r\n"
285                   "Connection: Close\r\n\r\n",
286                   host, port, mmsh->request_seq++, mms->stream_num, stream_selection);
287    av_freep(&stream_selection);
288    if (err < 0) {
289        av_log(NULL, AV_LOG_ERROR, "Build play request failed!\n");
290        goto fail;
291    }
292    av_dlog(NULL, "out_buffer is %s", headers);
293    av_opt_set(mms->mms_hd->priv_data, "headers", headers, 0);
294
295    err = ffurl_connect(mms->mms_hd, NULL);
296    if (err) {
297          goto fail;
298    }
299
300    err = get_http_header_data(mmsh);
301    if (err) {
302        av_log(NULL, AV_LOG_ERROR, "Get http header data failed!\n");
303        goto fail;
304    }
305
306    av_dlog(NULL, "Connection successfully open\n");
307    return 0;
308fail:
309    av_freep(&stream_selection);
310    mmsh_close(h);
311    av_dlog(NULL, "Connection failed with error %d\n", err);
312    return err;
313}
314
315static int handle_chunk_type(MMSHContext *mmsh)
316{
317    MMSContext *mms = &mmsh->mms;
318    int res, len = 0;
319    ChunkType chunk_type;
320    chunk_type = get_chunk_header(mmsh, &len);
321
322    switch (chunk_type) {
323    case CHUNK_TYPE_END:
324        mmsh->chunk_seq = 0;
325        av_log(NULL, AV_LOG_ERROR, "Stream ended!\n");
326        return AVERROR(EIO);
327    case CHUNK_TYPE_STREAM_CHANGE:
328        mms->header_parsed = 0;
329        if (res = get_http_header_data(mmsh)) {
330            av_log(NULL, AV_LOG_ERROR,"Stream changed! Failed to get new header!\n");
331            return res;
332        }
333        break;
334    case CHUNK_TYPE_DATA:
335        return read_data_packet(mmsh, len);
336    default:
337        av_log(NULL, AV_LOG_ERROR, "Recv other type packet %d\n", chunk_type);
338        return AVERROR_INVALIDDATA;
339    }
340    return 0;
341}
342
343static int mmsh_read(URLContext *h, uint8_t *buf, int size)
344{
345    int res = 0;
346    MMSHContext *mmsh = h->priv_data;
347    MMSContext *mms   = &mmsh->mms;
348    do {
349        if (mms->asf_header_read_size < mms->asf_header_size) {
350            // copy asf header into buffer
351            res = ff_mms_read_header(mms, buf, size);
352        } else {
353            if (!mms->remaining_in_len && (res = handle_chunk_type(mmsh)))
354                return res;
355            res = ff_mms_read_data(mms, buf, size);
356        }
357    } while (!res);
358    return res;
359}
360
361URLProtocol ff_mmsh_protocol = {
362    .name           = "mmsh",
363    .url_open       = mmsh_open,
364    .url_read       = mmsh_read,
365    .url_close      = mmsh_close,
366    .priv_data_size = sizeof(MMSHContext),
367    .flags          = URL_PROTOCOL_FLAG_NETWORK,
368};
369