Bakken & Baeck logo

Swift and C interoperability | Igor Ranieri

Bringing transparency to opaque pointers

I’ve been writing iOS apps and libraries for cryptography and FinTech for the last couple of years. Most existing libraries and frameworks are implemented in C, which shouldn’t surprise me, as it’s still considered the most efficient and performant way of writing platform agnostic code. If we want to use Swift, this leaves us with two options: wrapping everything inside ObjC wrappers that are exposed to Swift, or operating directly on those C APIs.

Of course, cryptography isn't the only realm of mostly C or C++ libraries. Amongst others, we'll see that image manipulation, computer vision and natural language processing also rely heavily on them.

Swift provides us two main ways to deal with pointers, manual memory management and the C-interoperability collection of classes, structs and methods. When faced with a C library, with the objective of minimising instances of doing the same work twice, I opted for the latter.

The first gives us the ability to manipulate memory directly, and the second, some way of operating on basic C types.

At first glance, they can seem a bit daunting. When should I use an UnsafeRawPointer, or an OpaquePointer? And what's the difference again? And all those types with unsafe right there in the name – that can't be safe, right? (🥁)

What's what

As the name implies, unsafe pointer types are unsafe, meaning that you're responsible for how it handles memory: allocating, accessing, deallocating. It’s all on you! 🙀

So let’s go over them.

OpaquePointer

Opaque pointers, are used to represent C pointers to types that can’t be represented in Swift, for example incomplete (C) structs. But one of the main uses I've found for it was as an intermediary type when I needed to cast a pointer as a different type of pointer.

var privateKey = [UInt8](repeating: 0, count: length)
var randomData = Data.generateSecureRandomData(count: length)
memcpy(&privateKey, &randomData, length)

privateKey[0] &= 248
privateKey[31] &= 127
privateKey[31] |= 64

let privateKeyPointer = UnsafeMutablePointer<ec_private_key>(OpaquePointer(privateKey))

Here, we create an OpaquePointer to our array of UInt8 that would come to represent a specific data structure unknown to the compiler at the time – an ec_private_key. If a C operation changes the nature of a pointer, Swift doesn't really know about it, so we have to handle that manually.

UnsafeRawPointer

Well, that's all nice, but what happens when we don't really know, or don't need to know, what kind of data we're pointing too? Or what if it changes?

With the UnsafeRawPointer, we can access untyped data. It's also very useful when you need to send it to different C-backed methods that expect differently named yet equivalent data types (this happens way too often, due to the nature of C types).

let pointer = UnsafeMutableRawPointer(address)
a_c_function(pointer.assumingMemoryBound(to: some_type.self)
b_c_function(pointer.assumingMemoryBound(to: equivalent_type_in_diff_library.self)

UnsafePointer and UnsafeBufferPointer

UnsafePointer, UnsafeBufferPointer and their mutable variants are responsible for holding a memory reference to a specific, known ahead of time, C type.

The first, to a specific data structure.

let private_key: UnsafePointer<ec_private_key> // holds a pointer to a ec_private_key C structure.

The latter, to a non-owning collection interface to a buffer of elements stored contiguously in memory. In most cases that means a reference to the start of an array of a known type.

/* Something like this in C */
int[10] int_ary;
// Can come to us as…
let int_ary_buffer: UnsafeBufferPointer<Int>

// Which we could use to create a Data instance, for example:
let data = Data(bytes: int_ary_buffer, count: int_ary_buffer_len, deallocator: .free)

Usage

How do we actually make use of this? Here are a few examples:

Creating a new C struct pointer from a C function:

Sometimes we need to use values created through a C function, for instance when creating encryption key pairs, for that UnsafeMutablePointer can be of great use:

var aPointer: UnsafeMutablePointer<some_c_struct>?

guard allocate_and_populate(&aPointer, self.sourceStuff) == 0,
  let pointer = aPointer
  else {
    throw .allocationFailed // error declared elsewhere
  }

// now we can use pointer and a non-optional value, that's been manipulated by a C function.
// we can access the struct properties through the `pointee` accessor.
pointer.pointee.c_struct_field

Applying transformations to a Swift struct through a C function, includes copying **Data** bytes to an array of primitive data types (**UInt8** in this case**):**

Sometimes we need to use a C function to sign a piece of data. Passing a Data structure back and forth between Swift and C can be cumbersome.

func sign(data: Data) -> Data throws {
  // create some random data
  let length = 64
  let randomData = Data.generateSecureRandomData(count: length)

  // create an "empty" array of bytes, copy the input data there.
  var message = [UInt8](repeating: 0, count: data.count)
  data.copyBytes(to: &message, count: data.count)

  // create another empty array of bytes, copy the random data there.
  var randomBytes = [UInt8](repeating: 0, count: randomData.count)
  randomData.copyBytes(to: &randomBytes, count: randomData.count)

  // create yet another empty array of bytes, this will hold our resulting signature
  var signatureBuffer = [UInt8](repeating: 0, count: length)
  // Cast our private key pointer to the type expected by the C function.
  let privateKey = UnsafeMutablePointer<UInt8>(OpaquePointer(self.keyPairPointer.pointee.private_key))

  // use Curve25519 to sign our data.
  guard curve25519_sign(&signatureBuffer, privateKey, message, UInt(data.count), randomBytes) >= 0
    else {
      throw .cantSignError
    }

  // returns our signature wrapped inside a Swift Data structure, abstracting all the C stuff away.
  return Data(bytes: signatureBuffer, count: length)
}

Allocating a C struct directly, and populating it's properties manually:

And sometimes we want more control over how a C struct gets populated, or how its memory is managed on our end. In that the case, allocating it and manually populating it can work great:

// Generate data from String
guard let data = name.data(using: .utf8) else {
  fatalError()
}

// Allocate a new instance. Capacity is usually 1, meaning we only want to allocate memory for a single instance.
let person = UnsafeMutablePointer<c_person>.allocate(capacity: 1)

// assign values to its properties
person.pointee.name_len = data.count

// Now we'll populate an array of `char` (same as `UInt8` in Swift/C) with the bytes of our name data structure.
let bytes = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)
data.copyBytes(to: bytes, count: data.count)

bytes.withMemoryRebound(to: Int8.self, capacity: data.count) { pointer in
  person.pointee.name = UnsafePointer(pointer)
}

/*
Now we have a c_person!

c_person {
  char* name;
  int name_len;
}
*/

But here we first our real head scratcher: All of this example code can silently fail. As soon as we exit the scope all the C structures will be instantly deallocated. Just like that, all your pointers will be nulled. So if you’re running an asynchronous process or abstracting your interop code inside a model, all of your C operations could be happening on nulled pointers. We need to ensure that our C structures remain in memory. But how?

You might have noticed that Swift does not give us any direct way incrementing / decrementing the reference counter. No good old retain / release calls. We're faced with a new question: when do I want this to be released from memory? If the answer is "as soon as I've exited this scope" then you're in luck, as that's exactly what's going to happen by default.

Most of us will need this in memory for a bit longer than that and we'll need to hold on to those values. An easy way to do that is by assigning them to a property. If we’re abstracting all our C interop code inside a model, that model will need to hold on to function arguments as well. That will bound our C stuff to the lifecycle of a particular class or struct, and it's much easier to manage.

class SessionRecord: NSObject {
    // We store it as a property here to bound its lifecycle to this object's lifecycle.
    private var sessionRecordPointer: UnsafeMutablePointer<session_record>

    init(data: Data, context: Context) {
        let data = data as NSData

        var recordPointer: UnsafeMutablePointer<session_record>?

        guard session_record_deserialize(&recordPointer, data.bytes.assumingMemoryBound(to: UInt8.self), data.length, context) >= 0,
          let record = recordPointer else {
            fatalError("Could not deserialize session record")
        }

        self.sessionRecordPointer = record

        super.init()
    }
}

All’s well that ends wel– EXC_BAD_ACCESS

Even for C veterans this stuff is hard. It’s hard to write, read, test and difficult to ensure that memory is correctly managed. At the end of the day, the best we can do is to double-check everything and test it and test it again, until it works just right.