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 <datasource/MediaHTTP.h>
25 #include <datasource/FileSource.h>
26 #include <media/DataSource.h>
27 #include <media/MediaHTTPConnection.h>
28 #include <media/MediaHTTPService.h>
29 #include <media/stagefright/foundation/ABuffer.h>
30 #include <media/stagefright/foundation/ADebug.h>
31 #include <openssl/aes.h>
32 #include <openssl/md5.h>
33 #include <utils/Mutex.h>
34 #include <inttypes.h>
35
36 namespace android {
37
HTTPDownloader(const sp<MediaHTTPService> & httpService,const KeyedVector<String8,String8> & headers)38 HTTPDownloader::HTTPDownloader(
39 const sp<MediaHTTPService> &httpService,
40 const KeyedVector<String8, String8> &headers) :
41 mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
42 mExtraHeaders(headers),
43 mDisconnecting(false) {
44 }
45
reconnect()46 void HTTPDownloader::reconnect() {
47 AutoMutex _l(mLock);
48 mDisconnecting = false;
49 }
50
disconnect()51 void HTTPDownloader::disconnect() {
52 {
53 AutoMutex _l(mLock);
54 mDisconnecting = true;
55 }
56 mHTTPDataSource->disconnect();
57 }
58
isDisconnecting()59 bool 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 */
fetchBlock(const char * url,sp<ABuffer> * out,int64_t range_offset,int64_t range_length,uint32_t block_size,String8 * actualUrl,bool reconnect)80 ssize_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 if (copy->data() == NULL) {
161 android_errorWriteLog(0x534e4554, "68399439");
162 ALOGE("not enough memory to download: requesting %zu + %zu",
163 buffer->size(), bufferRemaining);
164 return NO_MEMORY;
165 }
166 memcpy(copy->data(), buffer->data(), buffer->size());
167 copy->setRange(0, buffer->size());
168
169 buffer = copy;
170 }
171
172 size_t maxBytesToRead = bufferRemaining;
173 if (range_length >= 0) {
174 int64_t bytesLeftInRange = range_length - buffer->size();
175 if (bytesLeftInRange < 0) {
176 ALOGE("range_length %" PRId64 " wrapped around", range_length);
177 return ERROR_OUT_OF_RANGE;
178 } else if (bytesLeftInRange < (int64_t)maxBytesToRead) {
179 maxBytesToRead = bytesLeftInRange;
180
181 if (bytesLeftInRange == 0) {
182 break;
183 }
184 }
185 }
186
187 // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
188 // to help us break out of the loop.
189 ssize_t n = mDataSource->readAt(
190 buffer->size(), buffer->data() + buffer->size(),
191 maxBytesToRead);
192
193 if (isDisconnecting()) {
194 return ERROR_NOT_CONNECTED;
195 }
196
197 if (n < 0) {
198 return n;
199 }
200
201 if (n == 0) {
202 break;
203 }
204
205 buffer->setRange(0, buffer->size() + (size_t)n);
206 bytesRead += n;
207 }
208
209 *out = buffer;
210 if (actualUrl != NULL) {
211 *actualUrl = mDataSource->getUri();
212 if (actualUrl->isEmpty()) {
213 *actualUrl = url;
214 }
215 }
216
217 return bytesRead;
218 }
219
fetchFile(const char * url,sp<ABuffer> * out,String8 * actualUrl)220 ssize_t HTTPDownloader::fetchFile(
221 const char *url, sp<ABuffer> *out, String8 *actualUrl) {
222 ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
223
224 // close off the connection after use
225 mHTTPDataSource->disconnect();
226
227 return err;
228 }
229
fetchPlaylist(const char * url,uint8_t * curPlaylistHash,bool * unchanged)230 sp<M3UParser> HTTPDownloader::fetchPlaylist(
231 const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
232 ALOGV("fetchPlaylist '%s'", url);
233
234 *unchanged = false;
235
236 sp<ABuffer> buffer;
237 String8 actualUrl;
238 ssize_t err = fetchFile(url, &buffer, &actualUrl);
239
240 // close off the connection after use
241 mHTTPDataSource->disconnect();
242
243 if (err <= 0) {
244 return NULL;
245 }
246
247 // MD5 functionality is not available on the simulator, treat all
248 // playlists as changed.
249
250 #if defined(__ANDROID__)
251 uint8_t hash[16];
252
253 MD5_CTX m;
254 MD5_Init(&m);
255 MD5_Update(&m, buffer->data(), buffer->size());
256
257 MD5_Final(hash, &m);
258
259 if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
260 // playlist unchanged
261 *unchanged = true;
262
263 return NULL;
264 }
265 #endif
266
267 sp<M3UParser> playlist =
268 new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
269
270 if (playlist->initCheck() != OK) {
271 ALOGE("failed to parse .m3u8 playlist");
272
273 return NULL;
274 }
275
276 #if defined(__ANDROID__)
277 if (curPlaylistHash != NULL) {
278
279 memcpy(curPlaylistHash, hash, sizeof(hash));
280 }
281 #endif
282
283 return playlist;
284 }
285
286 } // namespace android
287