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
35 namespace android {
36
HTTPDownloader(const sp<IMediaHTTPService> & httpService,const KeyedVector<String8,String8> & headers)37 HTTPDownloader::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
reconnect()45 void HTTPDownloader::reconnect() {
46 AutoMutex _l(mLock);
47 mDisconnecting = false;
48 }
49
disconnect()50 void HTTPDownloader::disconnect() {
51 {
52 AutoMutex _l(mLock);
53 mDisconnecting = true;
54 }
55 mHTTPDataSource->disconnect();
56 }
57
isDisconnecting()58 bool 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 */
fetchBlock(const char * url,sp<ABuffer> * out,int64_t range_offset,int64_t range_length,uint32_t block_size,String8 * actualUrl,bool reconnect)79 ssize_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
fetchFile(const char * url,sp<ABuffer> * out,String8 * actualUrl)210 ssize_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
fetchPlaylist(const char * url,uint8_t * curPlaylistHash,bool * unchanged)220 sp<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