Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/MediaResource.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "MediaResource.h"
8
#include "mozilla/DebugOnly.h"
9
#include "mozilla/Logging.h"
10
#include "mozilla/MathAlgorithms.h"
11
#include "mozilla/SystemGroup.h"
12
#include "mozilla/ErrorNames.h"
13
14
using mozilla::media::TimeUnit;
15
16
#undef ILOG
17
18
mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex");
19
// Debug logging macro with object pointer and class name.
20
#define ILOG(msg, ...)                                                         \
21
0
  DDMOZ_LOG(                                                                   \
22
0
    gMediaResourceIndexLog, mozilla::LogLevel::Debug, msg, ##__VA_ARGS__)
23
24
namespace mozilla {
25
26
void
27
MediaResource::Destroy()
28
0
{
29
0
  // Ensures we only delete the MediaResource on the main thread.
30
0
  if (NS_IsMainThread()) {
31
0
    delete this;
32
0
    return;
33
0
  }
34
0
  nsresult rv = SystemGroup::Dispatch(
35
0
    TaskCategory::Other,
36
0
    NewNonOwningRunnableMethod(
37
0
      "MediaResource::Destroy", this, &MediaResource::Destroy));
38
0
  MOZ_ALWAYS_SUCCEEDS(rv);
39
0
}
40
41
NS_IMPL_ADDREF(MediaResource)
42
NS_IMPL_RELEASE_WITH_DESTROY(MediaResource, Destroy())
43
44
static const uint32_t kMediaResourceIndexCacheSize = 8192;
45
static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize),
46
              "kMediaResourceIndexCacheSize cache size must be a power of 2");
47
48
MediaResourceIndex::MediaResourceIndex(MediaResource* aResource)
49
  : mResource(aResource)
50
  , mOffset(0)
51
  , mCacheBlockSize(aResource->ShouldCacheReads()
52
                      ? kMediaResourceIndexCacheSize
53
                      : 0)
54
  , mCachedOffset(0)
55
  , mCachedBytes(0)
56
  , mCachedBlock(MakeUnique<char[]>(mCacheBlockSize))
57
0
{
58
0
  DDLINKCHILD("resource", aResource);
59
0
}
60
61
nsresult
62
MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
63
0
{
64
0
  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
65
0
66
0
  // We purposefuly don't check that we may attempt to read past
67
0
  // mResource->GetLength() as the resource's length may change over time.
68
0
69
0
  nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
70
0
  if (NS_FAILED(rv)) {
71
0
    return rv;
72
0
  }
73
0
  mOffset += *aBytes;
74
0
  if (mOffset < 0) {
75
0
    // Very unlikely overflow; just return to position 0.
76
0
    mOffset = 0;
77
0
  }
78
0
  return NS_OK;
79
0
}
80
81
static nsCString
82
ResultName(nsresult aResult)
83
0
{
84
0
  nsCString name;
85
0
  GetErrorName(aResult, name);
86
0
  return name;
87
0
}
88
89
nsresult
90
MediaResourceIndex::ReadAt(int64_t aOffset,
91
                           char* aBuffer,
92
                           uint32_t aCount,
93
                           uint32_t* aBytes)
94
0
{
95
0
  if (mCacheBlockSize == 0) {
96
0
    return UncachedReadAt(aOffset, aBuffer, aCount, aBytes);
97
0
  }
98
0
99
0
  *aBytes = 0;
100
0
101
0
  if (aCount == 0) {
102
0
    return NS_OK;
103
0
  }
104
0
105
0
  const int64_t endOffset = aOffset + aCount;
106
0
  if (aOffset < 0 || endOffset < aOffset) {
107
0
    return NS_ERROR_ILLEGAL_VALUE;
108
0
  }
109
0
110
0
  const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1);
111
0
112
0
  if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset &&
113
0
      mCachedOffset < endOffset) {
114
0
    // There is data in the cache that is not completely before aOffset and not
115
0
    // completely after endOffset, so it could be usable (with potential top-up).
116
0
    if (aOffset < mCachedOffset) {
117
0
      // We need to read before the cached data.
118
0
      const uint32_t toRead = uint32_t(mCachedOffset - aOffset);
119
0
      MOZ_ASSERT(toRead > 0);
120
0
      MOZ_ASSERT(toRead < aCount);
121
0
      uint32_t read = 0;
122
0
      nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
123
0
      if (NS_FAILED(rv)) {
124
0
        ILOG("ReadAt(%" PRIu32 "@%" PRId64
125
0
             ") uncached read before cache -> %s, %" PRIu32,
126
0
             aCount,
127
0
             aOffset,
128
0
             ResultName(rv).get(),
129
0
             *aBytes);
130
0
        return rv;
131
0
      }
132
0
      *aBytes = read;
133
0
      if (read < toRead) {
134
0
        // Could not read everything we wanted, we're done.
135
0
        ILOG("ReadAt(%" PRIu32 "@%" PRId64
136
0
             ") uncached read before cache, incomplete -> OK, %" PRIu32,
137
0
             aCount,
138
0
             aOffset,
139
0
             *aBytes);
140
0
        return NS_OK;
141
0
      }
142
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64
143
0
           ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32
144
0
           "@%" PRId64 "...",
145
0
           aCount,
146
0
           aOffset,
147
0
           read,
148
0
           aCount - read,
149
0
           aOffset + read);
150
0
      aOffset += read;
151
0
      aBuffer += read;
152
0
      aCount -= read;
153
0
      // We should have reached the cache.
154
0
      MOZ_ASSERT(aOffset == mCachedOffset);
155
0
    }
156
0
    MOZ_ASSERT(aOffset >= mCachedOffset);
157
0
158
0
    // We've reached our cache.
159
0
    const uint32_t toCopy =
160
0
      std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset));
161
0
    // Note that we could in fact be just after the last byte of the cache, in
162
0
    // which case we can't actually read from it! (But we will top-up next.)
163
0
    if (toCopy != 0) {
164
0
      memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy);
165
0
      *aBytes += toCopy;
166
0
      aCount -= toCopy;
167
0
      if (aCount == 0) {
168
0
        // All done!
169
0
        ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32
170
0
             ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32,
171
0
             aCount,
172
0
             aOffset,
173
0
             toCopy,
174
0
             mCachedBytes,
175
0
             mCachedOffset,
176
0
             *aBytes);
177
0
        return NS_OK;
178
0
      }
179
0
      aOffset += toCopy;
180
0
      aBuffer += toCopy;
181
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32
182
0
           " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32
183
0
           "@%" PRId64 "...",
184
0
           aCount + toCopy,
185
0
           aOffset - toCopy,
186
0
           toCopy,
187
0
           mCachedBytes,
188
0
           mCachedOffset,
189
0
           aCount,
190
0
           aOffset);
191
0
    }
192
0
193
0
    if (aOffset - 1 >= lastBlockOffset) {
194
0
      // We were already reading cached data from the last block, we need more
195
0
      // from it -> try to top-up, read what we can, and we'll be done.
196
0
      MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes);
197
0
      MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize);
198
0
      return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
199
0
    }
200
0
201
0
    // We were not in the last block (but we may just have crossed the line now)
202
0
    MOZ_ASSERT(aOffset <= lastBlockOffset);
203
0
    // Continue below...
204
0
  } else if (aOffset >= lastBlockOffset) {
205
0
    // There was nothing we could get from the cache.
206
0
    // But we're already in the last block -> Cache or read what we can.
207
0
    // Make sure to invalidate the cache first.
208
0
    mCachedBytes = 0;
209
0
    return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
210
0
  }
211
0
212
0
  // If we're here, either there was nothing usable in the cache, or we've just
213
0
  // read what was in the cache but there's still more to read.
214
0
215
0
  if (aOffset < lastBlockOffset) {
216
0
    // We need to read before the last block.
217
0
    // Start with an uncached read up to the last block.
218
0
    const uint32_t toRead = uint32_t(lastBlockOffset - aOffset);
219
0
    MOZ_ASSERT(toRead > 0);
220
0
    MOZ_ASSERT(toRead < aCount);
221
0
    uint32_t read = 0;
222
0
    nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
223
0
    if (NS_FAILED(rv)) {
224
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64
225
0
           ") uncached read before last block failed -> %s, %" PRIu32,
226
0
           aCount,
227
0
           aOffset,
228
0
           ResultName(rv).get(),
229
0
           *aBytes);
230
0
      return rv;
231
0
    }
232
0
    if (read == 0) {
233
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64
234
0
           ") uncached read 0 before last block -> OK, %" PRIu32,
235
0
           aCount,
236
0
           aOffset,
237
0
           *aBytes);
238
0
      return NS_OK;
239
0
    }
240
0
    *aBytes += read;
241
0
    if (read < toRead) {
242
0
      // Could not read everything we wanted, we're done.
243
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64
244
0
           ") uncached read before last block, incomplete -> OK, %" PRIu32,
245
0
           aCount,
246
0
           aOffset,
247
0
           *aBytes);
248
0
      return NS_OK;
249
0
    }
250
0
    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32
251
0
         " before last block, remaining: %" PRIu32 "@%" PRId64 "...",
252
0
         aCount,
253
0
         aOffset,
254
0
         read,
255
0
         aCount - read,
256
0
         aOffset + read);
257
0
    aOffset += read;
258
0
    aBuffer += read;
259
0
    aCount -= read;
260
0
  }
261
0
262
0
  // We should just have reached the start of the last block.
263
0
  MOZ_ASSERT(aOffset == lastBlockOffset);
264
0
  MOZ_ASSERT(aCount <= mCacheBlockSize);
265
0
  // Make sure to invalidate the cache first.
266
0
  mCachedBytes = 0;
267
0
  return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
268
0
}
269
270
nsresult
271
MediaResourceIndex::CacheOrReadAt(int64_t aOffset,
272
                                  char* aBuffer,
273
                                  uint32_t aCount,
274
                                  uint32_t* aBytes)
275
0
{
276
0
  // We should be here because there is more data to read.
277
0
  MOZ_ASSERT(aCount > 0);
278
0
  // We should be in the last block, so we shouldn't try to read past it.
279
0
  MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize);
280
0
281
0
  const int64_t length = GetLength();
282
0
  // If length is unknown (-1), look at resource-cached data.
283
0
  // If length is known and equal or greater than requested, also look at
284
0
  // resource-cached data.
285
0
  // Otherwise, if length is known but same, or less than(!?), requested, don't
286
0
  // attempt to access resource-cached data, as we're not expecting it to ever
287
0
  // be greater than the length.
288
0
  if (length < 0 || length >= aOffset + aCount) {
289
0
    // Is there cached data covering at least the requested range?
290
0
    const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset);
291
0
    if (cachedDataEnd >= aOffset + aCount) {
292
0
      // Try to read as much resource-cached data as can fill our local cache.
293
0
      // Assume we can read as much as is cached without blocking.
294
0
      const uint32_t cacheIndex = IndexInCache(aOffset);
295
0
      const uint32_t toRead =
296
0
        uint32_t(std::min(cachedDataEnd - aOffset,
297
0
                          int64_t(mCacheBlockSize - cacheIndex)));
298
0
      MOZ_ASSERT(toRead >= aCount);
299
0
      uint32_t read = 0;
300
0
      // We would like `toRead` if possible, but ok with at least `aCount`.
301
0
      nsresult rv = UncachedRangedReadAt(
302
0
        aOffset, &mCachedBlock[cacheIndex], aCount, toRead - aCount, &read);
303
0
      if (NS_SUCCEEDED(rv)) {
304
0
        if (read == 0) {
305
0
          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
306
0
               "..%" PRIu32 "@%" PRId64
307
0
               ") to top-up succeeded but read nothing -> OK anyway",
308
0
               aCount,
309
0
               aOffset,
310
0
               aCount,
311
0
               toRead,
312
0
               aOffset);
313
0
          // Couldn't actually read anything, but didn't error out, so count
314
0
          // that as success.
315
0
          return NS_OK;
316
0
        }
317
0
        if (mCachedOffset + mCachedBytes == aOffset) {
318
0
          // We were topping-up the cache, just update its size.
319
0
          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
320
0
               "..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32
321
0
               "...",
322
0
               aCount,
323
0
               aOffset,
324
0
               aCount,
325
0
               toRead,
326
0
               aOffset,
327
0
               read);
328
0
          mCachedBytes += read;
329
0
        } else {
330
0
          // We were filling the cache from scratch, save new cache information.
331
0
          ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
332
0
               "..%" PRIu32 "@%" PRId64
333
0
               ") to fill cache succeeded to read %" PRIu32 "...",
334
0
               aCount,
335
0
               aOffset,
336
0
               aCount,
337
0
               toRead,
338
0
               aOffset,
339
0
               read);
340
0
          mCachedOffset = aOffset;
341
0
          mCachedBytes = read;
342
0
        }
343
0
        // Copy relevant part into output.
344
0
        uint32_t toCopy = std::min(aCount, read);
345
0
        memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy);
346
0
        *aBytes += toCopy;
347
0
        ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64
348
0
             " -> OK, %" PRIu32,
349
0
             aCount,
350
0
             aOffset,
351
0
             toCopy,
352
0
             aOffset,
353
0
             *aBytes);
354
0
        // We may not have read all that was requested, but we got everything
355
0
        // we could get, so we're done.
356
0
        return NS_OK;
357
0
      }
358
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
359
0
           "..%" PRIu32 "@%" PRId64
360
0
           ") failed: %s, will fallback to blocking read...",
361
0
           aCount,
362
0
           aOffset,
363
0
           aCount,
364
0
           toRead,
365
0
           aOffset,
366
0
           ResultName(rv).get());
367
0
      // Failure during reading. Note that this may be due to the cache
368
0
      // changing between `GetCachedDataEnd` and `ReadAt`, so it's not
369
0
      // totally unexpected, just hopefully rare; but we do need to handle it.
370
0
371
0
      // Invalidate part of cache that may have been partially overridden.
372
0
      if (mCachedOffset + mCachedBytes == aOffset) {
373
0
        // We were topping-up the cache, just keep the old untouched data.
374
0
        // (i.e., nothing to do here.)
375
0
      } else {
376
0
        // We were filling the cache from scratch, invalidate cache.
377
0
        mCachedBytes = 0;
378
0
      }
379
0
    } else {
380
0
      ILOG("ReadAt(%" PRIu32 "@%" PRId64
381
0
           ") - no cached data, will fallback to blocking read...",
382
0
           aCount,
383
0
           aOffset);
384
0
    }
385
0
  } else {
386
0
    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64
387
0
         " (%s), will fallback to blocking read as the caller requested...",
388
0
         aCount,
389
0
         aOffset,
390
0
         length,
391
0
         length < 0 ? "unknown" : "too short!");
392
0
  }
393
0
  uint32_t read = 0;
394
0
  nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read);
395
0
  if (NS_SUCCEEDED(rv)) {
396
0
    *aBytes += read;
397
0
    ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32
398
0
         " bytes -> %s, %" PRIu32,
399
0
         aCount,
400
0
         aOffset,
401
0
         read,
402
0
         ResultName(rv).get(),
403
0
         *aBytes);
404
0
  } else {
405
0
    ILOG("ReadAt(%" PRIu32 "@%" PRId64
406
0
         ") - fallback uncached read failed -> %s, %" PRIu32,
407
0
         aCount,
408
0
         aOffset,
409
0
         ResultName(rv).get(),
410
0
         *aBytes);
411
0
  }
412
0
  return rv;
413
0
}
414
415
nsresult
416
MediaResourceIndex::UncachedReadAt(int64_t aOffset,
417
                                   char* aBuffer,
418
                                   uint32_t aCount,
419
                                   uint32_t* aBytes) const
420
0
{
421
0
  if (aOffset < 0) {
422
0
    return NS_ERROR_ILLEGAL_VALUE;
423
0
  }
424
0
  if (aCount == 0) {
425
0
    *aBytes = 0;
426
0
    return NS_OK;
427
0
  }
428
0
  return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
429
0
}
430
431
nsresult
432
MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
433
                                         char* aBuffer,
434
                                         uint32_t aRequestedCount,
435
                                         uint32_t aExtraCount,
436
                                         uint32_t* aBytes) const
437
0
{
438
0
  uint32_t count = aRequestedCount + aExtraCount;
439
0
  if (aOffset < 0 || count < aRequestedCount) {
440
0
    return NS_ERROR_ILLEGAL_VALUE;
441
0
  }
442
0
  if (count == 0) {
443
0
    *aBytes = 0;
444
0
    return NS_OK;
445
0
  }
446
0
  return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
447
0
}
448
449
nsresult
450
MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
451
0
{
452
0
  switch (aWhence) {
453
0
    case SEEK_SET:
454
0
      break;
455
0
    case SEEK_CUR:
456
0
      aOffset += mOffset;
457
0
      break;
458
0
    case SEEK_END:
459
0
    {
460
0
      int64_t length = mResource->GetLength();
461
0
      if (length == -1 || length - aOffset < 0) {
462
0
        return NS_ERROR_FAILURE;
463
0
      }
464
0
      aOffset = mResource->GetLength() - aOffset;
465
0
    }
466
0
      break;
467
0
    default:
468
0
      return NS_ERROR_FAILURE;
469
0
  }
470
0
471
0
  if (aOffset < 0) {
472
0
    return NS_ERROR_ILLEGAL_VALUE;
473
0
  }
474
0
  mOffset = aOffset;
475
0
476
0
  return NS_OK;
477
0
}
478
479
already_AddRefed<MediaByteBuffer>
480
MediaResourceIndex::MediaReadAt(int64_t aOffset, uint32_t aCount) const
481
0
{
482
0
  NS_ENSURE_TRUE(aOffset >= 0, nullptr);
483
0
  RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
484
0
  bool ok = bytes->SetLength(aCount, fallible);
485
0
  NS_ENSURE_TRUE(ok, nullptr);
486
0
487
0
  uint32_t bytesRead = 0;
488
0
  nsresult rv = mResource->ReadAt(
489
0
    aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
490
0
  NS_ENSURE_SUCCESS(rv, nullptr);
491
0
492
0
  bytes->SetLength(bytesRead);
493
0
  return bytes.forget();
494
0
}
495
496
already_AddRefed<MediaByteBuffer>
497
MediaResourceIndex::CachedMediaReadAt(int64_t aOffset, uint32_t aCount) const
498
0
{
499
0
  RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
500
0
  bool ok = bytes->SetLength(aCount, fallible);
501
0
  NS_ENSURE_TRUE(ok, nullptr);
502
0
  char* curr = reinterpret_cast<char*>(bytes->Elements());
503
0
  nsresult rv = mResource->ReadFromCache(curr, aOffset, aCount);
504
0
  NS_ENSURE_SUCCESS(rv, nullptr);
505
0
  return bytes.forget();
506
0
}
507
508
// Get the length of the stream in bytes. Returns -1 if not known.
509
// This can change over time; after a seek operation, a misbehaving
510
// server may give us a resource of a different length to what it had
511
// reported previously --- or it may just lie in its Content-Length
512
// header and give us more or less data than it reported. We will adjust
513
// the result of GetLength to reflect the data that's actually arriving.
514
int64_t
515
MediaResourceIndex::GetLength() const
516
0
{
517
0
  return mResource->GetLength();
518
0
}
519
520
uint32_t
521
MediaResourceIndex::IndexInCache(int64_t aOffsetInFile) const
522
0
{
523
0
  const uint32_t index = uint32_t(aOffsetInFile) & (mCacheBlockSize - 1);
524
0
  MOZ_ASSERT(index == aOffsetInFile % mCacheBlockSize);
525
0
  return index;
526
0
}
527
528
int64_t
529
MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile) const
530
0
{
531
0
  const int64_t offset = aOffsetInFile & ~(int64_t(mCacheBlockSize) - 1);
532
0
  MOZ_ASSERT(offset == aOffsetInFile - IndexInCache(aOffsetInFile));
533
0
  return offset;
534
0
}
535
536
} // namespace mozilla
537
538
// avoid redefined macro in unified build
539
#undef ILOG