read method
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));
}));
}));
}