withUnsafeBytes

Is the following code the best that can be done, the nesting adds a lot of visual complexity.

Is there a better way to handle the "C" parameters?


Swift3:

var macData = Data(count: Int(CC_SHA1_DIGEST_LENGTH))

let _ = macData.withUnsafeMutableBytes {macBytes in

messageData.withUnsafeBytes {messageBytes in

keyData.withUnsafeBytes {keyBytes in

CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1),

keyBytes, keyData.count,

messageBytes, messageData.count,

macBytes);

}

}

}

Replies

If you don't mind having an Obj-C file (.m) in your target, it might be clearer to move the above into a global function written in Obj-C, which takes 2 NSData parameters and returns a NSData result. The calling code and the function will then be pretty simple and clear.

Seems the Standard Library designers of Swift think a few nested withUnsafeSomething should be acceptable.


A global function `withUnsafePointers(_:_:_:_:)` has been removed from Swift Standard Library, and the error message of using it says:

error: 'withUnsafePointers' is unavailable: use nested withUnsafePointer(to:_:) instead


As you may already know, you still can use `NSMutableData` or `NSData`, if you prefer:

    let macData = NSMutableData(length: Int(CC_SHA1_DIGEST_LENGTH))!
    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1),
           keyData.bytes, keyData.length,
           messageData.bytes, messageData.length,
           macData.mutableBytes)


But, those changes -- pointer retrieving properties to `withUnsafeSomething` method -- are intentionally made, which may help Swift compiler generate better code or something like that.


I expect more Swifty interface for CC functions will be provided in the near future (we should send a feature request?), like GCD has become Dispatch library in Swift 3.

And until then, we may need to work with nested `withUnsafeSomething`s or to use good old `NSData` and `NSMutableData`.

I have an ObjC version, the point was to create a totally Swift version.

>> As you may already know, you still can use `NSMutableData` or `NSData`, if you prefer:


I'll point out that this approach is actually a bit dangerous, even in equivalent Obj-C code. Those pointers returned by the ".bytes" or ".mutableBytes" method calls are interior pointers, and hence not memory managed. It can be difficult to predict how long they will be valid, especially given that NSData has many possibile internal representations of its data, and some are more weird than others. On top of that, Swift's memory semantics are different from Obj-C's, which means that even things that were valid in Obj-C are not necessarily valid when re-coded into the Swift equivelent of the exact same code.


That's exactly why Swift has methods like "withUnsafeBytes". The compiler is able to take measures to ensure that the interior pointer is valid for the entire closure.


Chances are that the Swift NSData code you propose will work fine, but it may produce very occasional, hard-to-debug failures.


FWIW.

I too expect native Swift3 interfaces as well as some updates such as AES-GCM soon.

My gloal is to use ionbly Swift3 data types such as Data.


Thanks for letting me know I am not totally crazy.

I'll point out that this approach is actually a bit dangerous, even in equivalent Obj-C code.


So, you mean your proposal, move the above into a global function written in Obj-C , is as gangerous. Am I taking it right?

Potentially, but it's a better known issue in Obj-C and clang provides variable annotations that can be used to mitigate the problem.


What concerns me more in this case is the mixing of memory models between Swift and Obj-C. It may be fine, but it's more of an unknown quantity.

Sorry, but I don't know the known issue. Can you tell what is it?


And about the mixing of memory models between Swift and Obj-C, your proposal -- combining ObjC file will cause the same issue, no?

I'll give the simplest example, because that's the one I can remember. With code like this:


NSMutableData* data = [NSMutableData dataWithLength: 10 * sizeof (float)];
let floats = (float*) data.mutableBytes;
for (int i = 0; i < 10; i++)
     floats [i] = i; // imagine some useful calculation being done with "floats"
return;


you'll likely crash in the "for" loop. That's because the compiler regards line 02 as the end of the lifetime of variable 'data' and releases the object before line 03, causing it to be deallocated, invalidating the "floats" pointer. There are more subtle examples of an interior pointer becoming invalidated — you usually find out about them the hard way, through a rare, unrepeatable bug or crash.


The different memory models have different rules for pointer aliasing. (Basically, in Swift, I believe you can't do it except with explicitly "raw" pointer types. Obj-C follows the C rules, which are more complicated.) If you're passing a couple of object pointers from Swift code to an Obj-C function, you're not going to run into an aliasing issue. If you're passing interior pointers across a Swift/C interface, as your version of the original code did, you might.

Thanks for showing a good example.


So, you are saying, with this code:

let macData = NSMutableData(length: Int(CC_SHA1_DIGEST_LENGTH))!
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1),
       keyData.bytes, keyData.length,
       messageData.bytes, messageData.length,
       macData.mutableBytes)

Swift might release each `NSData` or `NSMutableData` instances before finishing `CCHMac`.

It's not totally impossible, depending on the order that code generation is done, although probably not for 'macData', if that's used subsequently.

Thanks for clarifying what you think dangerous about this situation.

Personally I’d wrap this code in a Swift-friendly function like this:

func sha1HMAC(message: Data, key: Data) -> Data

At that point all the call sites for the code will be pleasant and all the ugliness is tightly isolated. If you found yourself implementing this function a lot, it’d be worthwhile writing some glue to help with that, but for the odd Common Crypto function it’s easier to just wrap it and move on to something more interesting.

ps You probably want to get rid of that semicolon (-:

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"