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
35namespace android {
36
37HTTPDownloader::HTTPDownloader(
38        const sp<IMediaHTTPService> &httpService,
39        const KeyedVector<String8, String8> &headers) :
40    mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
41    mExtraHeaders(headers),
42    mDisconnecting(false) {
43}
44
45void HTTPDownloader::reconnect() {
46    AutoMutex _l(mLock);
47    mDisconnecting = false;
48}
49
50void HTTPDownloader::disconnect() {
51    {
52        AutoMutex _l(mLock);
53        mDisconnecting = true;
54    }
55    mHTTPDataSource->disconnect();
56}
57
58bool HTTPDownloader::isDisconnecting() {
59    AutoMutex _l(mLock);
60    return mDisconnecting;
61}
62
63/*
64 * Illustration of parameters:
65 *
66 * 0      `range_offset`
67 * +------------+-------------------------------------------------------+--+--+
68 * |            |                                 | next block to fetch |  |  |
69 * |            | `source` handle => `out` buffer |                     |  |  |
70 * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
71 * |            |<----------- `range_length` / buffer capacity ----------->|  |
72 * |<------------------------------ file_size ------------------------------->|
73 *
74 * Special parameter values:
75 * - range_length == -1 means entire file
76 * - block_size == 0 means entire range
77 *
78 */
79ssize_t HTTPDownloader::fetchBlock(
80        const char *url, sp<ABuffer> *out,
81        int64_t range_offset, int64_t range_length,
82        uint32_t block_size, /* download block size */
83        String8 *actualUrl,
84        bool reconnect /* force connect HTTP when resuing source */) {
85    if (isDisconnecting()) {
86        return ERROR_NOT_CONNECTED;
87    }
88
89    off64_t size;
90
91    if (reconnect) {
92        if (!strncasecmp(url, "file://", 7)) {
93            mDataSource = new FileSource(url + 7);
94        } else if (strncasecmp(url, "http://", 7)
95                && strncasecmp(url, "https://", 8)) {
96            return ERROR_UNSUPPORTED;
97        } else {
98            KeyedVector<String8, String8> headers = mExtraHeaders;
99            if (range_offset > 0 || range_length >= 0) {
100                headers.add(
101                        String8("Range"),
102                        String8(
103                            AStringPrintf(
104                                "bytes=%lld-%s",
105                                range_offset,
106                                range_length < 0
107                                    ? "" : AStringPrintf("%lld",
108                                            range_offset + range_length - 1).c_str()).c_str()));
109            }
110
111            status_t err = mHTTPDataSource->connect(url, &headers);
112
113            if (isDisconnecting()) {
114                return ERROR_NOT_CONNECTED;
115            }
116
117            if (err != OK) {
118                return err;
119            }
120
121            mDataSource = mHTTPDataSource;
122        }
123    }
124
125    status_t getSizeErr = mDataSource->getSize(&size);
126
127    if (isDisconnecting()) {
128        return ERROR_NOT_CONNECTED;
129    }
130
131    if (getSizeErr != OK) {
132        size = 65536;
133    }
134
135    sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
136    if (*out == NULL) {
137        buffer->setRange(0, 0);
138    }
139
140    ssize_t bytesRead = 0;
141    // adjust range_length if only reading partial block
142    if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
143        range_length = buffer->size() + block_size;
144    }
145    for (;;) {
146        // Only resize when we don't know the size.
147        size_t bufferRemaining = buffer->capacity() - buffer->size();
148        if (bufferRemaining == 0 && getSizeErr != OK) {
149            size_t bufferIncrement = buffer->size() / 2;
150            if (bufferIncrement < 32768) {
151                bufferIncrement = 32768;
152            }
153            bufferRemaining = bufferIncrement;
154
155            ALOGV("increasing download buffer to %zu bytes",
156                 buffer->size() + bufferRemaining);
157
158            sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
159            memcpy(copy->data(), buffer->data(), buffer->size());
160            copy->setRange(0, buffer->size());
161
162            buffer = copy;
163        }
164
165        size_t maxBytesToRead = bufferRemaining;
166        if (range_length >= 0) {
167            int64_t bytesLeftInRange = range_length - buffer->size();
168            if (bytesLeftInRange < (int64_t)maxBytesToRead) {
169                maxBytesToRead = bytesLeftInRange;
170
171                if (bytesLeftInRange == 0) {
172                    break;
173                }
174            }
175        }
176
177        // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
178        // to help us break out of the loop.
179        ssize_t n = mDataSource->readAt(
180                buffer->size(), buffer->data() + buffer->size(),
181                maxBytesToRead);
182
183        if (isDisconnecting()) {
184            return ERROR_NOT_CONNECTED;
185        }
186
187        if (n < 0) {
188            return n;
189        }
190
191        if (n == 0) {
192            break;
193        }
194
195        buffer->setRange(0, buffer->size() + (size_t)n);
196        bytesRead += n;
197    }
198
199    *out = buffer;
200    if (actualUrl != NULL) {
201        *actualUrl = mDataSource->getUri();
202        if (actualUrl->isEmpty()) {
203            *actualUrl = url;
204        }
205    }
206
207    return bytesRead;
208}
209
210ssize_t HTTPDownloader::fetchFile(
211        const char *url, sp<ABuffer> *out, String8 *actualUrl) {
212    ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
213
214    // close off the connection after use
215    mHTTPDataSource->disconnect();
216
217    return err;
218}
219
220sp<M3UParser> HTTPDownloader::fetchPlaylist(
221        const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
222    ALOGV("fetchPlaylist '%s'", url);
223
224    *unchanged = false;
225
226    sp<ABuffer> buffer;
227    String8 actualUrl;
228    ssize_t err = fetchFile(url, &buffer, &actualUrl);
229
230    // close off the connection after use
231    mHTTPDataSource->disconnect();
232
233    if (err <= 0) {
234        return NULL;
235    }
236
237    // MD5 functionality is not available on the simulator, treat all
238    // playlists as changed.
239
240#if defined(HAVE_ANDROID_OS)
241    uint8_t hash[16];
242
243    MD5_CTX m;
244    MD5_Init(&m);
245    MD5_Update(&m, buffer->data(), buffer->size());
246
247    MD5_Final(hash, &m);
248
249    if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
250        // playlist unchanged
251        *unchanged = true;
252
253        return NULL;
254    }
255
256    if (curPlaylistHash != NULL) {
257        memcpy(curPlaylistHash, hash, sizeof(hash));
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    return playlist;
271}
272
273}  // namespace android
274