1/*
2 * Copyright 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//#define LOG_NDEBUG 0
18#define LOG_TAG "HTTPDownloader"
19#include <utils/Log.h>
20
21#include "HTTPDownloader.h"
22#include "M3UParser.h"
23
24#include <media/IMediaHTTPConnection.h>
25#include <media/IMediaHTTPService.h>
26#include <media/stagefright/foundation/ABuffer.h>
27#include <media/stagefright/foundation/ADebug.h>
28#include <media/stagefright/MediaHTTP.h>
29#include <media/stagefright/DataSource.h>
30#include <media/stagefright/FileSource.h>
31#include <openssl/aes.h>
32#include <openssl/md5.h>
33#include <utils/Mutex.h>
34#include <inttypes.h>
35
36namespace android {
37
38HTTPDownloader::HTTPDownloader(
39        const sp<IMediaHTTPService> &httpService,
40        const KeyedVector<String8, String8> &headers) :
41    mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
42    mExtraHeaders(headers),
43    mDisconnecting(false) {
44}
45
46void HTTPDownloader::reconnect() {
47    AutoMutex _l(mLock);
48    mDisconnecting = false;
49}
50
51void HTTPDownloader::disconnect() {
52    {
53        AutoMutex _l(mLock);
54        mDisconnecting = true;
55    }
56    mHTTPDataSource->disconnect();
57}
58
59bool HTTPDownloader::isDisconnecting() {
60    AutoMutex _l(mLock);
61    return mDisconnecting;
62}
63
64/*
65 * Illustration of parameters:
66 *
67 * 0      `range_offset`
68 * +------------+-------------------------------------------------------+--+--+
69 * |            |                                 | next block to fetch |  |  |
70 * |            | `source` handle => `out` buffer |                     |  |  |
71 * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
72 * |            |<----------- `range_length` / buffer capacity ----------->|  |
73 * |<------------------------------ file_size ------------------------------->|
74 *
75 * Special parameter values:
76 * - range_length == -1 means entire file
77 * - block_size == 0 means entire range
78 *
79 */
80ssize_t HTTPDownloader::fetchBlock(
81        const char *url, sp<ABuffer> *out,
82        int64_t range_offset, int64_t range_length,
83        uint32_t block_size, /* download block size */
84        String8 *actualUrl,
85        bool reconnect /* force connect HTTP when resuing source */) {
86    if (isDisconnecting()) {
87        return ERROR_NOT_CONNECTED;
88    }
89
90    off64_t size;
91
92    if (reconnect) {
93        if (!strncasecmp(url, "file://", 7)) {
94            mDataSource = new FileSource(url + 7);
95        } else if (strncasecmp(url, "http://", 7)
96                && strncasecmp(url, "https://", 8)) {
97            return ERROR_UNSUPPORTED;
98        } else {
99            KeyedVector<String8, String8> headers = mExtraHeaders;
100            if (range_offset > 0 || range_length >= 0) {
101                headers.add(
102                        String8("Range"),
103                        String8(
104                            AStringPrintf(
105                                "bytes=%lld-%s",
106                                range_offset,
107                                range_length < 0
108                                    ? "" : AStringPrintf("%lld",
109                                            range_offset + range_length - 1).c_str()).c_str()));
110            }
111
112            status_t err = mHTTPDataSource->connect(url, &headers);
113
114            if (isDisconnecting()) {
115                return ERROR_NOT_CONNECTED;
116            }
117
118            if (err != OK) {
119                return err;
120            }
121
122            mDataSource = mHTTPDataSource;
123        }
124    }
125
126    status_t getSizeErr = mDataSource->getSize(&size);
127
128    if (isDisconnecting()) {
129        return ERROR_NOT_CONNECTED;
130    }
131
132    if (getSizeErr != OK) {
133        size = 65536;
134    }
135
136    sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
137    if (*out == NULL) {
138        buffer->setRange(0, 0);
139    }
140
141    ssize_t bytesRead = 0;
142    // adjust range_length if only reading partial block
143    if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
144        range_length = buffer->size() + block_size;
145    }
146    for (;;) {
147        // Only resize when we don't know the size.
148        size_t bufferRemaining = buffer->capacity() - buffer->size();
149        if (bufferRemaining == 0 && getSizeErr != OK) {
150            size_t bufferIncrement = buffer->size() / 2;
151            if (bufferIncrement < 32768) {
152                bufferIncrement = 32768;
153            }
154            bufferRemaining = bufferIncrement;
155
156            ALOGV("increasing download buffer to %zu bytes",
157                 buffer->size() + bufferRemaining);
158
159            sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
160            memcpy(copy->data(), buffer->data(), buffer->size());
161            copy->setRange(0, buffer->size());
162
163            buffer = copy;
164        }
165
166        size_t maxBytesToRead = bufferRemaining;
167        if (range_length >= 0) {
168            int64_t bytesLeftInRange = range_length - buffer->size();
169            if (bytesLeftInRange < 0) {
170                ALOGE("range_length %" PRId64 " wrapped around", range_length);
171                return ERROR_OUT_OF_RANGE;
172            } else if (bytesLeftInRange < (int64_t)maxBytesToRead) {
173                maxBytesToRead = bytesLeftInRange;
174
175                if (bytesLeftInRange == 0) {
176                    break;
177                }
178            }
179        }
180
181        // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
182        // to help us break out of the loop.
183        ssize_t n = mDataSource->readAt(
184                buffer->size(), buffer->data() + buffer->size(),
185                maxBytesToRead);
186
187        if (isDisconnecting()) {
188            return ERROR_NOT_CONNECTED;
189        }
190
191        if (n < 0) {
192            return n;
193        }
194
195        if (n == 0) {
196            break;
197        }
198
199        buffer->setRange(0, buffer->size() + (size_t)n);
200        bytesRead += n;
201    }
202
203    *out = buffer;
204    if (actualUrl != NULL) {
205        *actualUrl = mDataSource->getUri();
206        if (actualUrl->isEmpty()) {
207            *actualUrl = url;
208        }
209    }
210
211    return bytesRead;
212}
213
214ssize_t HTTPDownloader::fetchFile(
215        const char *url, sp<ABuffer> *out, String8 *actualUrl) {
216    ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
217
218    // close off the connection after use
219    mHTTPDataSource->disconnect();
220
221    return err;
222}
223
224sp<M3UParser> HTTPDownloader::fetchPlaylist(
225        const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
226    ALOGV("fetchPlaylist '%s'", url);
227
228    *unchanged = false;
229
230    sp<ABuffer> buffer;
231    String8 actualUrl;
232    ssize_t err = fetchFile(url, &buffer, &actualUrl);
233
234    // close off the connection after use
235    mHTTPDataSource->disconnect();
236
237    if (err <= 0) {
238        return NULL;
239    }
240
241    // MD5 functionality is not available on the simulator, treat all
242    // playlists as changed.
243
244#if defined(__ANDROID__)
245    uint8_t hash[16];
246
247    MD5_CTX m;
248    MD5_Init(&m);
249    MD5_Update(&m, buffer->data(), buffer->size());
250
251    MD5_Final(hash, &m);
252
253    if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
254        // playlist unchanged
255        *unchanged = true;
256
257        return NULL;
258    }
259#endif
260
261    sp<M3UParser> playlist =
262        new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
263
264    if (playlist->initCheck() != OK) {
265        ALOGE("failed to parse .m3u8 playlist");
266
267        return NULL;
268    }
269
270#if defined(__ANDROID__)
271    if (curPlaylistHash != NULL) {
272
273        memcpy(curPlaylistHash, hash, sizeof(hash));
274    }
275#endif
276
277    return playlist;
278}
279
280}  // namespace android
281