read method

  1. @override
Future<ResourceTry<ByteData>> read({
  1. IntRange? range,
})

Reads the bytes at the given range.

When range is null, the whole content is returned. Out-of-range indexes are clamped to the available length automatically.

Implementation

@override
Future<ResourceTry<ByteData>> read({IntRange? range}) async {
  if (range == null) {
    return license.decryptFully(await resource.read(), false);
  }

  IntRange range2 = IntRange(max(0, range.first), range.last);

  if (range2.isEmpty) {
    return ResourceTry.success(ByteData(0));
  }
  return resource.length().then((res) =>
      res.flatMapWaitResource((encryptedLength) async {
        // encrypted data is shifted by AES_BLOCK_SIZE because of IV and
        // the previous block must be provided to perform XOR on intermediate blocks
        int encryptedStart = range.first.floorMultipleOf(aesBlockSize);
        int encryptedEndExclusive =
            (range.last + 1).ceilMultipleOf(aesBlockSize) + aesBlockSize;

        return resource
            .read(range: IntRange(encryptedStart, encryptedEndExclusive))
            .then((res) => res.mapWait((encryptedData) async {
                  String href = (await link()).href;
                  ByteData bytes = (await license.decrypt(encryptedData))
                      .getOrElse((failure) => throw Exception(
                          "Can't decrypt the content at: $href, $failure"));

                  // exclude the bytes added to match a multiple of AES_BLOCK_SIZE
                  int sliceStart = range.first - encryptedStart;

                  // was the last block read to provide the desired range
                  bool lastBlockRead =
                      encryptedLength - encryptedEndExclusive <= aesBlockSize;
                  int rangeLength =
                      await _computeRangeLength(lastBlockRead, range);
                  return bytes.sliceArray(IntRange(sliceStart, rangeLength));
                }));
      }));
}