SecRandomCopyBytes

Небезопасная игра со Swift

В Apple Security Framework есть функция SecRandomCopyBytes(), которая позволяет создать массив криптографически безопасных случайных байтов. Есть много способов генерировать случайные данные в iOS и OS X, но я хотел использовать этот, потому что он подходит для криптографических целей. Конечно, я мог бы использовать arc4random(), но тогда этот пост в блоге был бы не таким интересным. Итак, SecRandomCopyBytes(). Так получилось, что этот проект, над которым я работаю, написан на Swift. И я понятия не имел, как вызвать его из Swift. Итак, начнем с простого.

Раунд 1: Цель-C

Что я знаю, так это то, как это вызвать из Objective-C.

Objective-C:
int32_t randomNumber = 0;
SecRandomCopyBytes(kSecRandomDefault, 4, (uint8_t*) &randomNumber);

Это было не так сложно. Во-первых, мы объявляем 32-битное целое число (и инициализируем его нулем). Затем мы вызываем SecRandomCopyBytes, передавая 4 в качестве счетчика (4 байта — это 32 бита), а также место в памяти, где должны храниться случайные байты: адрес целого числа случайных чисел, которое мы только что объявили. Успех, мы выиграли первый раунд.

Раунд 2: Первая быстрая попытка

Думаю, на этом можно было бы закончить, ведь вызвать Objective-C из Swift совсем не проблема. Но это жульничество, чему бы я научился? Итак, давайте попробуем это в Swift.

Swift:
var randomNumber : Int32 = 0
SecRandomCopyBytes(kSecRandomDefault, 4, &randomNumber)

К сожалению, это приводит к ошибке компилятора: «Int32» не совпадает с «UInt8». Красота строго типизированных языков! Мы проиграли этот раунд.

Раунд 3: Небезопасная игра

Итак, если это строго типизированный язык, то какой тип нам нужен? Глядя на документацию, мы подсказываем, что нам нужна переменная типа UnsafeMutablePointer‹UInt8›. Так как же нам взять Int32 и использовать его как UnsafeMutablePointer‹UInt8›? Фактически, для этого есть два шага: (1) получить UnsafeMutablePointer и (2) преобразовать его из указателя на UInt32 в указатель на UInt8.

withUnsafeMutablePointer

Получается, что для первого шага нам нужно использовать withUnsafeMutablePointer. При попытке выяснить, как это работает, Swift iBooks бесполезны; там это не задокументировано. Итак, давайте посмотрим на определение в Xcode. Мы читаем:

Swift:
/// Invokes `body` with an `UnsafeMutablePointer` to `arg` and returns the
/// result. Useful for calling Objective-C APIs that take "in/out" 
/// parameters (and default-constructible "out" parameters) by pointer
func withUnsafeMutablePointer<T, Result>(inout arg: T, body: (UnsafeMutablePointer<T>) -> Result) -> Result

Давайте проанализируем это. withUnsafeMutablePointer — это метод шаблона, который использует два типа шаблонов: T и Result. У него два аргумента: первый — входной параметр, называемый arg, который имеет тип T. Второй — замыкание, называемое body. Это замыкание принимает один аргумент типа UnsafeMutablePointer‹T› и имеет возвращаемый тип Result. Наконец, сам withUnsafeMutablePointer возвращает тип Result.

Правильно. Но что он делает? Прочитав его пару раз, вы обнаружите, что документация на самом деле говорит это точно (хотя и лаконично): она берет ссылку на переменную Swift и делает ее доступной как UnsafeMutablePointer внутри замыкания. Итак, теперь мы можем ввести что-то вроде этого:

Swift:
var randomNumber : Int32 = 0 withUnsafeMutablePointer(&randomNumber, { (randomNumberPointer) -> Void in
    var proof : UnsafeMutablePointer<Int32> = randomNumberPointer 
})

Это компилируется просто для того, чтобы доказать вам, что randomNumberPointer действительно является UnsafeMutablePointer‹Int32›.

небезопасный биткаст

Итак, мы подошли ко второму шагу: нам не нужен указатель на Int32, нам нужен указатель на UInt8, чтобы вызвать SecRandomCopyBytes. Введите unsafeBitCast.

Будучи программистом на языке Objective-C, вы, вероятно, знаете, что указатель — это просто адрес области памяти, и вас не волнует тип указателя, главное, чтобы размер области памяти был правильным. Итак, в Objective-C вы можете просто использовать приведение типов:

Swift:
int32_t number = 0
uint8_t *numberPointer = (uint8_t*) &number

На самом деле, если вы пропустите приведение типа, компилятор выдаст только предупреждение и все равно позволит вам запустить программу. Приведение типа существует для того, чтобы мы могли сказать компилятору, что мы знаем лучше, чем он, ему не нужно беспокоиться, мы знаем, что делаем.

Функция unsafeBitCast является эквивалентом преобразования типа в Swift. Давайте посмотрим на определение:

Swift:
/// Returns the the bits of `x`, interpreted as having type `U`. 
///
/// .. Caution:: Breaks the guarantees of Swift's type system; use 
/// with extreme care. There's almost always a better way to do
/// anything.
///
func unsafeBitCast<T, U>(x: T, _: U.Type) -> U

Таким образом, он принимает параметр x типа T и анонимный параметр, который сам является типом, и возвращает параметр x, приведенный к этому типу. Давайте применим это к нашему коду. Нам не нужно возвращать значение из withUnsafeMutablePointer, поэтому в качестве типа результата я выбираю Void. Обратите внимание на конструкцию UnsafeMutablePointer‹UInt8›.self, чтобы указать тип, к которому мы хотим привести указатель.

Swift:
var randomNumber : Int32 = 0 withUnsafeMutablePointer(&randomNumber, { (randomNumberPointer) -> Void in
    var castedPointer = unsafeBitCast(randomNumberPointer, UnsafeMutablePointer<UInt8>.self)
    SecRandomCopyBytes(kSecRandomDefault, 4, castedPointer)
})

Вот и все. Теперь randomNumber содержит 32 бита случайных данных. Ура!

Завершение

Я надеюсь, что вы узнали что-то здесь, я знаю, что сделал! Мне нравится программировать на Swift, и я рад, что у Apple хватает смелости продолжать улучшать его. Однако, когда вы доберетесь до границ между Swift и (Objective-)C, иногда это может быть проблемой.

И последнее замечание: после прочтения предупреждения о unsafeBitCast (почти всегда есть лучший способ сделать что-либо), у меня действительно возник мучительный вопрос, есть ли лучший способ в этом случае, и что он из себя представляет. было бы, но это, может быть, в другой раз. Предложения и обратная связь приветствуются!

Первоначально опубликовано на labs.ind.ie.