2

I need to asynchronously decrypt a large file with RNCryptor on iOS (so as to show a progress bar). I have found no example anywhere, and thus have tried what I guessed was right, but... what I've come up with doesn't work : the decryptor’s handler is never called, and the thread crashed with EXC_BAD_ADDRESS after sending all data at the end of the function.

NSOutputStream *decryptedStream = [NSOutputStream outputStreamToFileAtPath:decryptedPath append:NO];
[decryptedStream open];

NSUInteger totalBytesToRead = [[[NSFileManager defaultManager] attributesOfItemAtPath:tempPath error:nil] fileSize];
__block NSUInteger totalBytesRead = 0;

LOG("I've got %d bytes to decrypt.", totalBytesToRead);

RNDecryptor *decryptor = [[RNDecryptor alloc] initWithPassword:SNCatalogPassword handler:^(RNCryptor *cryptor, NSData *data) {
    totalBytesRead += data.length;
    [decryptedStream write:data.bytes maxLength:data.length];

    LOG("Decrypted %d bytes : %d / %d bytes treated", data.length, totalBytesRead, totalBytesToRead);

    if (cryptor.isFinished)
    {
        //proceed
        LOG("Done with decrypting.");

        [decryptedStream close];

    }
}];

// Feed data to the decryptor
NSInputStream *cryptedStream = [NSInputStream inputStreamWithFileAtPath:tempPath];
[cryptedStream open];
while (cryptedStream.hasBytesAvailable)
{
    uint8_t buf[4096];
    NSUInteger bytesRead = [cryptedStream read:buf maxLength:4096];
    NSData *data = [NSData dataWithBytes:buf length:bytesRead];
    LOG("Sending %d bytes to decryptor...", bytesRead);

    dispatch_async(dispatch_get_main_queue(), ^{
        [decryptor addData:data];
    });
}

LOG("Sent everything.");
[decryptor finish];
[cryptedStream close];

(Obvsiouly, tempPath is the path to the crypted file ; decryptedPath is the path where decrypted data should be written).

Also I'm new to ARC so this may be a memory- or dispatch-related problem.

Thanks for any help.

Cyrille
  • 25,014
  • 12
  • 67
  • 90

2 Answers2

3

I ran into the same issue today, and it seems to be happening due to the recent deprecation of dispatch_get_current_queue() in iOS6.

By changing [RNCryptor initWithHandler:] to create a new queue the decryption works correctly.

NSString *responseQueueName = [@"net.robnapier.response." stringByAppendingString:NSStringFromClass([self class])];
_responseQueue = dispatch_queue_create([responseQueueName UTF8String], NULL);

You can find the fix and an associated unit test (based on your code) on the async_decrypt branch of my fork on github.

Commit on csteynberg/RNCryptor

Calman
  • 546
  • 5
  • 6
  • That did the trick, thanks a million times. You should consider sending a merge request to rnapier! (And welcome to SO, too.) – Cyrille Jan 18 '13 at 08:01
  • You're welcome. I've already sent a pull request to Rob Napier. Just wanted to make sure you had something you can try. – Calman Jan 18 '13 at 08:12
  • Though the test is not really asynchronous. I was expecting something as "read chunk -> decrypt -> read chunk -> decrypt -> ... -> close"; instead I have "read chunk -> read chunk -> ... -> read chunk -> decrypt -> decrypt -> decrypt -> decrypt -> close". – Cyrille Jan 18 '13 at 08:28
  • Well it *is* asynchronous, but I feel like it needs to put the whole file in memory before decrypting it. That's potentially huge for me. – Cyrille Jan 18 '13 at 08:35
  • maybe you should use different queues ? if you use only one (synchronous) queue all happens in order. Another option would be to use a concurrent queue (adding DISPATCH_QUEUE_CONCURRENT instead of NULL as param) – aurelien.n Jan 18 '13 at 09:20
  • @Cyrille You don't need to load all the data into memory before decrypting it. I'm using NSURLConnection connection:didReceiveData: to download files and do the decryption as the data streams in. I haven't had any issues with files as large as 80MB and don't expect to see any with larger ones either. If you have a file on disk, you can use NSInputStream and just read out as many bytes as you want. – Calman Jan 19 '13 at 17:15
  • @aurelien I believe that CBC mode requires decryption to happen synchronously as each block depends on the block before it which is why I did not use DISPATCH_QUEUE_CONCURRENT. I wanted to make sure that none of the data arrives out of order. – Calman Jan 19 '13 at 17:17
  • Thanks Calman. That's what I intend to to, decrypt from a `NSInputStream` and write to a `NSOutputStream`. – Cyrille Jan 19 '13 at 18:50
  • Calman, aurelien.n: still did not manage to get a fully async method. Please see http://stackoverflow.com/questions/14481605/dispatch-queues-and-asynchronous-rncryptor – Cyrille Jan 23 '13 at 14:04
  • FYI: This pull request has been merged into RNCryptor. Thanks to @Calman for that. https://github.com/rnapier/RNCryptor/commit/ed2f5bc8c4942d956084a9acd51ef056b6747e33 – Rob Napier Jan 29 '13 at 15:25
2

your behaviour is due to asynchronous execution: you call [decryptor finish] before doing the calls to addData: To fix this you should replace

while (cryptedStream.hasBytesAvailable)
{
  //...

by

while (YES) {
  if (!cryptedStream.hasBytesAvailable) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [decryptor finish];
    });
    break;
  }
  //...

(and of course remove the existing call to [decryptor finish]) this way finish is always called after all the data was sent.

Regards

aurelien.n
  • 106
  • 3
  • I've tried this and the behavior is still the same. The RNCryptor handler is never entered, and the method crashed after logging "Sent everything". – Cyrille Jan 17 '13 at 18:40
  • Nevertheless, your point is correct. Without it the behavior is erratic too, though that was not the cause of the original problem. – Cyrille Jan 18 '13 at 08:02