readRange method

  1. @override
Future<Uint8List> readRange(
  1. String key, {
  2. required int start,
  3. required int length,
})
override

Read a byte range.

Returns bytes starting at start with the given length. For encrypted files, only the overlapping chunks are decrypted; for chunked files, only the overlapping chunk records are read.

Implementation

@override
Future<Uint8List> readRange(
  String key, {
  required int start,
  required int length,
}) async {
  checkNotDisposed();
  final info = await _inner.head(key);
  if (info == null) throw FileNotFoundError(key);

  if (info.metadata[_metaEncrypted] != 'true') {
    return _inner.readRange(key, start: start, length: length);
  }

  final encKey = await _keyResolver?.resolveKey(key);
  if (encKey == null) throw EncryptionKeyMissingError(key);

  final g = await _resolveGeometry(key, info);

  // Chunk-aware range read — only decrypt overlapping chunks
  final firstChunk = start ~/ g.chunkSize;
  final lastChunk = (start + length - 1) ~/ g.chunkSize;

  final buffer = BytesBuilder(copy: false);
  for (var i = firstChunk; i <= lastChunk && i < g.chunkCount; i++) {
    final isLastChunk = i == g.chunkCount - 1;
    final dataSize = isLastChunk
        ? g.originalSize - (i * g.chunkSize)
        : g.chunkSize;
    final chunkTotalSize = dataSize + _encryptor.chunkMacSize;
    final chunkStart =
        g.headerSize + (i * (g.chunkSize + _encryptor.chunkMacSize));

    final chunkData = await _inner.readRange(
      key,
      start: chunkStart,
      length: chunkTotalSize,
    );

    final decrypted = await _encryptor.decryptChunk(
      chunkData,
      encKey,
      g.nonce,
      i,
    );
    buffer.add(decrypted);
  }

  // Slice to the exact requested range. Clamp the end so an over-length
  // request returns the available bytes instead of throwing — matching the
  // clamping every other backend does.
  final chunkAlignedStart = firstChunk * g.chunkSize;
  final offsetInFirstChunk = start - chunkAlignedStart;
  final fullDecrypted = buffer.takeBytes();
  final end = (offsetInFirstChunk + length).clamp(0, fullDecrypted.length);

  return Uint8List.sublistView(fullDecrypted, offsetInFirstChunk, end);
}