6

I've updated to swift 5 and one of the dependencies I use won't compile in swift 5. I've fixed it, but now I'm getting 350+ deprecation warnings all over the file. They're all similar to this:

withUnsafeMutableBytes is deprecated: use withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R instead

And this is a snipit of the code (it's basically just calling a c library's functions):

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { kPtr in
    flutter_sodium.crypto_generichash_keygen(kPtr)
}

For reference, in the above crypto_generichash_keybytes() just returns a size_t and crypto_generichash_keygen's signature is void crypto_generichash_keygen(unsigned char k[crypto_generichash_KEYBYTES]);.

I figured out (as this answer states) that the way to get around this should be to call kPtr.baseAddress:

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { kPtr in
    flutter_sodium.crypto_generichash_keygen(kPtr.baseAddress)
}

as that should use the withUnsafeMutableBytes<ResultType> variant rather than the deprecated withUnsafeMutableBytes<ResultType, ContentType>. However, this instead results in the error

value of type 'UnsafeMutablePointer<_>' has no member 'baseAddress'.

If I explicitly specify the resultType and kPtr:

var k = Data(count: crypto_generichash_keybytes())
k.withUnsafeMutableBytes { (kPtr: UnsafeMutableRawBufferPointer) -> Void in
    flutter_sodium.crypto_generichash_keygen(kPtr.baseAddress)
}

I instead get

UnsafeMutableRawBufferPointer' is not convertible to 'UnsafeMutablePointer<_>'.

Are there any swift experts out there that can help me figure out the right way to do this? I know the warnings are just warnings, but I prefer to have code that compiles with no warnings.

I took a look at Swift 5.0: 'withUnsafeBytes' is deprecated: use `withUnsafeBytes<R>(...) before posting this question and it doesn't help my situation as I'm not loading the pointer but rather using the data. Also, I've done exactly what the documentation tells me to but that still isn't helping.

EDIT: To be a bit more clear, some of the 350+ warnings were related to code where the Data is allocated in the code, however some of them are where I receive Data from an external source. That looks something like this:

let args = call.arguments as! NSDictionary
let server_pk = (args["server_pk"] as! FlutterStandardTypedData).data
let server_sk = (args["server_sk"] as! FlutterStandardTypedData).data
let client_pk = (args["client_pk"] as! FlutterStandardTypedData).data

var rx = Data(count: flutter_sodium.crypto_kx_sessionkeybytes())
var tx = Data(count: flutter_sodium.crypto_kx_sessionkeybytes())
let ret = rx.withUnsafeMutableBytes { rxPtr in
  tx.withUnsafeMutableBytes { txPtr in
    server_pk.withUnsafeBytes { server_pkPtr in
      server_sk.withUnsafeBytes { server_skPtr in
        client_pk.withUnsafeBytes { client_pkPtr in
          flutter_sodium.crypto_kx_server_session_keys(rxPtr, txPtr, server_pkPtr, server_skPtr, client_pkPtr)
        }
      }
    }
  }
}

with the corresponding method call

SODIUM_EXPORT
int crypto_kx_client_session_keys(unsigned char rx[crypto_kx_SESSIONKEYBYTES],
                                  unsigned char tx[crypto_kx_SESSIONKEYBYTES],
                                  const unsigned char client_pk[crypto_kx_PUBLICKEYBYTES],
                                  const unsigned char client_sk[crypto_kx_SECRETKEYBYTES],
                                  const unsigned char server_pk[crypto_kx_PUBLICKEYBYTES])
            __attribute__ ((warn_unused_result));

(and I know that the code is not really optimal swift, but when dealing with interoperability between dart and swift this is what the flutter team came up with for how to do it).

When I asked the question I was trying to distill it down to the simplest case but that case had a specific answer which differs to the overall problem I'm having.

rmtmckenzie
  • 37,718
  • 9
  • 112
  • 99
  • 1
    Great that you asked this question with Swift 5 just being released. Made the transition easier for me. – Wyetro Mar 27 '19 at 22:11
  • Here is the way how I fixed it: https://stackoverflow.com/a/58008707/1151916 – Ramis Sep 19 '19 at 10:15

1 Answers1

10

I wouldn't use Data here – Data represents an untyped collection of "raw" bytes, however crypto_generichash_keygen wants a mutable pointer to typed memory. The reason why the UnsafeMutablePointer<T> variant of withUnsafeMutableBytes was deprecated is that it's fundamentally the wrong abstraction to be providing on untyped memory.

The simplest way to get a buffer of typed memory in Swift is with an Array:

var k = [UInt8](repeating: 0, count: crypto_generichash_keybytes())
flutter_sodium.crypto_generichash_keygen(&k)

You can always turn the resulting buffer into a Data value afterwards by saying Data(k).

Another option is to use an UnsafeMutableBufferPointer:

let k = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: crypto_generichash_keybytes())
defer {
  k.deallocate()
}
flutter_sodium.crypto_generichash_keygen(k.baseAddress!)
// Now use the buffer `k` – just make sure you finish using it before the end of
// the scope when `deallocate()` gets called!

Unlike Array, this avoids having to pre-fill the resulting buffer with zeros before being passed off to the C API, however this likely isn't of concern. But just like Array, you can turn such a buffer into a Data by just saying Data(k).


For cases where you get handed a Data value from some external source and need to pass it off to an API as a typed pointer, the simplest and safest option is to just turn it into an array before passing it by saying Array(someData).

For example:

let args = call.arguments as! NSDictionary
let server_pk = (args["server_pk"] as! FlutterStandardTypedData).data
let server_sk = (args["server_sk"] as! FlutterStandardTypedData).data
let client_pk = (args["client_pk"] as! FlutterStandardTypedData).data

var rx = [UInt8](repeating: 0, count: flutter_sodium.crypto_kx_sessionkeybytes())
var tx = [UInt8](repeating: 0, count: flutter_sodium.crypto_kx_sessionkeybytes())

flutter_sodium.crypto_kx_server_session_keys(
  &rx, &tx, Array(server_pk), Array(server_sk), Array(client_pk)
)

You probably could use withUnsafeBytes and call bindMemory on the underlying pointer, but I would discourage it, as it changes the type of the underlying memory which could subtly impact the soundness of any other Swift code sharing that memory due to the fact that you're switching out the type from under it.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Ah, that's interesting. I can see how using an array would be a more sensible option in this particular case. And since Data currently zeros the buffer anyways I don't think that's a performance concern for Array either - and I like the Array way of doing it better than fiddling around with UnsafeMutableBufferPointers. I always figure that if it has 'Unsafe' in the name it should be avoided. – rmtmckenzie Mar 27 '19 at 19:10
  • However, I'm also dealing with the case where I have `Data` objects passed directly to me (from Flutter's abstraction layer which receives the raw data from Dart code) - I should have been more clear about that in the question. In that case I don't really have the option to start with an array and am back to trying to use `withUnsafeMutableBytes` or `withUnsafeBytes`. – rmtmckenzie Mar 27 '19 at 19:11
  • I've updated my question with a bit more detail; as I said in the update I was trying to distill the problem down to the simplest case but and while I think your answer is 100% correct for the cases where I'm allocating `Data` myself, it doesn't help for the cases where I'm using a `Data` that I get from elsewhere. – rmtmckenzie Mar 27 '19 at 19:21
  • @rmtmckenzie Honestly the easiest way to deal with that would probably be to just convert the `Data` values you get into arrays before passing them off to the API, e.g `flutter_sodium.crypto_kx_server_session_keys(rxPtr, Array(tx), Array(server_pk), Array(server_sk), Array(client_pk))`. That also nicely eliminates the pyramid of doom. – Hamish Mar 27 '19 at 21:26
  • You *probably* could use `withUnsafeBytes` and call `bindMemory` on the underlying pointer, but I would discourage it, as it changes the type of the underlying memory which could subtly impact the soundness of other code sharing that memory by switching out the type from under it. – Hamish Mar 27 '19 at 21:26
  • Using Array to wrap the data works perfectly =). That's much simpler than I thought it would be and suits me just fine, thanks so much for your help!! And it'll be nice to get away from the 'pyramid of doom' =D – rmtmckenzie Mar 27 '19 at 21:39