transferBulkFromHub method

Future<Uint8List?> transferBulkFromHub({
  1. required TransferType txType,
  2. required int txSubType,
  3. required String destination,
  4. int maxChunkErrors = 50,
})

Implementation

Future<Uint8List?> transferBulkFromHub({
  required TransferType txType,
  required int txSubType,
  required String destination,
  int maxChunkErrors = 50,
}) async {
  if (!isConnected || connectedDeviceId == null) {
    return null;
  }

  final resetOk = await bulkReset();
  if (!resetOk) {
    return null;
  }

  final setup = await setupBulkTransferSingle(
    txType: txType,
    txSubType: txSubType,
    destination: destination,
    totalSizeBytes: 0,
    toHub: false,
  );
  if (setup == null) {
    return null;
  }

  final expectedTotalBytes = setup.totalSizeBytes;
  final assembled = BytesBuilder(copy: false);
  int sequenceId = 0;
  int errorCount = 0;
  final pendingAcks = Queue<int>();

  while (true) {
    if (expectedTotalBytes > 0 && assembled.length >= expectedTotalBytes) {
      break;
    }

    final ackBatch = pendingAcks.take(_bulkReadAckWindowSize).toList();

    final chunk = await sendCommand<BulkBytesChunk>(
      BulkReadBleCommandTx(
        sequenceId: sequenceId,
        txType: txType,
        ackIds: ackBatch,
      ),
    );

    if (chunk == null) {
      errorCount++;
      if (errorCount > maxChunkErrors) {
        return null;
      }
      continue;
    }

    _prunePendingBulkReadAcks(pendingAcks, chunk.nextExpectedAckId);

    if (!pendingAcks.contains(chunk.sequenceId)) {
      pendingAcks.add(chunk.sequenceId);
    }

    final hasEof = _hasEndOfFileError(chunk.errorCodes);
    final hasInvalidSequence = chunk.errorCodes.contains(
      CborHubCommandErrorKeys.invalidSequence,
    );
    final hasIllegalAck = chunk.errorCodes.contains(
      CborHubCommandErrorKeys.illegalAck,
    );

    if (hasEof) {
      break;
    }

    if (chunk.hasErrors && !hasInvalidSequence && !hasIllegalAck) {
      errorCount++;
      if (errorCount > maxChunkErrors) {
        return null;
      }
      continue;
    }

    if (chunk.bytes.isEmpty && chunk.checksum == 0) {
      errorCount++;
      if (errorCount > maxChunkErrors) {
        return null;
      }
      continue;
    }

    if (chunk.sequenceId != sequenceId) {
      errorCount++;
      if (errorCount > maxChunkErrors) {
        return null;
      }
      continue;
    }

    if (_fletcher32(chunk.bytes) != chunk.checksum) {
      errorCount++;
      if (errorCount > maxChunkErrors) {
        return null;
      }
      continue;
    }

    assembled.add(chunk.bytes);
    sequenceId++;
    errorCount = 0;
  }

  final flushedAcks = await _flushPendingBulkReadAcks(
    nextSequenceId: sequenceId,
    txType: txType,
    pendingAcks: pendingAcks,
  );
  if (!flushedAcks) {
    return null;
  }

  final bytes = assembled.toBytes();
  if (setup.totalCrc32 != null && setup.totalCrc32 != _crc32(bytes)) {
    return null;
  }

  return bytes;
}