Создание пользовательских преобразований
Несмотря на то, что OS X обеспечивает, много коллективной безопасности преобразовывают, можно также создать собственное. Эта глава показывает, как создать и использовать пользовательское преобразование.
Как упомянуто в предыдущих главах, преобразовывает, основываются на блоках и Grand Central Dispatch (GCD). Расписания GCD, которые каждый преобразовывает независимо, разрешение, преобразовывают для выполнения параллельно. Несмотря на то, что Вы не должны понимать GCD для записи пользовательского преобразования, действительно необходимо понять блоки. Для узнавания больше о блоках и GCD считайте Блоки Представления и Центральную Отгрузку.
Преобразовывает основываются на Базовых объектах Основы. Этот документ принимает элементарные знания того, как работают эти объекты. Для узнавания больше считайте Базовые Концепции проекта Основы.
Демонстрационное пользовательское преобразование, описанное в этой главе, CaesarXform.c
, преобразование, реализующее простого Цезаря (ROT-N) шифр. Полный исходный код для этого преобразования включен в конце этой главы в Полном Листинге кода.
Объявление имени преобразования
Все преобразовывает, нуждаются в уникальном имени для идентификации его при создании нового экземпляра. Как правило необходимо использовать именование обратного стиля DNS. Например, этот шифр вызывают com.apple.caesarcipher
.
Во-первых, создайте исходный файл и включайте (по крайней мере), следующие заголовки:
#include <CoreFoundation/CoreFoundation.h> |
#include <Security/SecCustomTransform.h> |
#include <Security/SecTransform.h> |
Затем объявите имя. Имя должно быть a CFStringRef
экземпляр, и должен быть объявлен как глобальная переменная в заголовочном файле так, чтобы он мог использоваться другим кодом. Для этого шифра описание имени показано ниже:
const CFStringRef kCaesarCipher = CFSTR("com.apple.caesarcipher"); |
Пишущий функцию создания преобразования, первую часть
Преобразование может взять любое число параметров, но последним параметром должен всегда быть указатель на a CFErrorRef
объект. Шифр Цезаря преобразовывает, берет только один параметр (ключ шифрования), который имеет тип CFIndex
потому что ключ является небольшим числом. (Полезный диапазон совпадает с диапазоном uint8_t
значение.) Таким образом функция объявляется следующим образом:
SecTransformRef CaesarTransformCreate(CFIndex k, CFErrorRef* error) |
Эта функция регистрирует преобразование, затем создает экземпляр его.
Регистрация преобразования инициализирует преобразование и говорит коду платформы, что это существует. Поскольку эта инициализация должна быть выполнена точно один раз во время времени жизни программы, этот пример использует GCD для размещения блока инициализации в очередь отгрузки точно один раз, как показано ниже:
dispatch_once(®isteredOK, ^{block}); |
Для функции нужен флаг, чтобы видеть, было ли преобразование инициализировано и должно возвратить значение, чтобы указать, что было успешно зарегистрировано преобразование. Эти переменные определяются вне блока, следующим образом:
__block Boolean result = 1; |
static dispatch_once_t registeredOK = 0; |
Обратите внимание на то, что переменная результата, как объявляли, была блоком (__block
) переменная так, чтобы это могло использоваться в блоке инициализации.
В блоке инициализации необходимо вызвать SecTransformRegister
с тремя параметрами: имя преобразования, функция реализации, связывающая действия преобразования с фактическими блоками обработчика и указатель на хранение для a CFErrorRef
объект. Например:
result = SecTransformRegister(kCaesarCipher, &CaesarImplementation, error); |
Если регистрация перестала работать, SecTransformRegister
возвращает булевскую переменную нам и изменяет CFErrorRef
объект передал в вызывающей стороной.
Путем Вы реализуете само преобразование, описан в следующем разделе.
Запись функции реализации преобразования
После записи функции создания можно реализовать само преобразование. Вы делаете это путем записи функции реализации. Эта функция вызвана каждый раз, когда новый экземпляр Вашего преобразования создается. Это возвращает блок экземпляра, в котором преобразование выполняет свою работу.
В ядре этой функции следующие утверждения:
SecTransformInstanceBlock instanceBlock = ^{instance-block}; |
return Block_copy(instanceBlock); |
В блоке экземпляра необходимо объявить любые переменные, в которых нуждается преобразование. Шифры могли бы использовать эти переменные для внутренних буферов, переменных состояния, и т.д. Шифр Цезаря преобразовывает, требует только одной переменной контекста, ключа:
__block int _key; |
Обратите внимание на то, что переменная, как объявляют, __block
, который заставляет его быть сохраненным в блоке. Группа преобразования может содержать многократные экземпляры преобразования, таким образом, все данные, определенные для экземпляра Вашего преобразования, должны инкапсулироваться в этом блоке экземпляра.
В дополнение к обеспечению хранения для значений, определенных для каждого экземпляра преобразования, блок экземпляра также устанавливает действие для каждого типа сообщения, который реализует преобразование. Шифр Цезаря преобразовывает поддержки только два действия:
Подпрограмма уведомления для установки атрибута. Это действие берет название атрибута (типа
SecTransformAttributeRef
, который, оказывается, aCFStringRef
объект) и aCFTypeRef
объект, содержащий значение.Шифр Цезаря преобразовывает поддержки только один атрибут, ключевой атрибут. Для непротиворечивости с другими преобразованиями это преобразование использует
kKeyAttributeName
постоянный для идентификации того ключа.result = SecTransformSetAttributeAction(ref,
kSecTransformActionAttributeNotification,
kKeyAttributeName,
^(SecTransformAttributeRef name,
CFTypeRef d){action-block});
Блок действия для этого действия (представленный заполнителем блока действия в последней строке выше) содержит код, который будет выполняться, когда установлен ключевой атрибут. В целях этого примера это использует
CFNumberGetValue
скопировать значение в основную переменную_key
. В полноценном внедрении это должно использоватьisKindOfClass:
если значение не имеет ожидаемого типа, метод для проверки типа и должен тогда возвратить надлежащую ошибку.ProcessData
действие. Это действие вызывают с aCFDataRef
возразите, когда новые данные станут готовыми обработать. Это действие возвращает aCFDataRef
содержа результаты.Когда это действие получает a
NULL
указатель как его ввод, это означает, что или вызывающая сторона или преобразование ранее в цепочке указали, что это не передаст дальнейших данных. Это обычно происходит, когда чтение преобразовывает, достигает конца его входного потока. В этой точке, если существует какой-либо окончательный результат для испускания, необходимо вычислить тот вывод и отправить его в выходной атрибут (kSecTransformOutputAttributeName
) перед возвратомNULL
от блока.result = SecTransformSetDataAction(ref,
kSecTransformActionProcessData,
^(CFTypeRef d){action-block});
Пример шифра Цезаря выделяет a
CFDataRef
использование экземпляраmalloc(3)
, шифрует простой текст, хранит результаты в том новом объекте и возвращает его.
Обратите внимание на то, что все подпрограммы действия вызывают с самым универсальным из всех Базовых объектов Основы, a CFTypeRef
объект. Чтобы быть максимально гибкими, можно использовать isKindOfClass:
метод для определения типа параметра.
Как исключение, код платформы фильтрует любые объекты CF, отправленные во входной атрибут, и передает только CFDataRef
объекты к Вашему действию. Таким образом не необходимо использовать isKindOfClass:
метод в Вашем ProcessData
действие. Можно безопасно предположить, что Вы будете отправлены a CFDataRef
объект (или NULL
). Необходимо возвратить a CFDataRef
объект, но необходимо бросить его к a CFTypeRef
объект.
Ошибки из-за неправильного обращения
Преобразовывает использование CFErrorRef
объекты сигнализировать и описать ошибки в выполнении. Любой блок или подпрограмма могут возвратить a CFErrorRef
возразите для сигнализации ошибки. Это является самым полезным в a ProcessData
действие. Самый прямой способ сигнализировать ошибку состоит в том, чтобы отправить a CFErrorRef
возразите против атрибута аварийного прекращения работы (kSecTransformAbortAttributeName
). Выполнение так закрывает цепочку преобразований.
Пишущий функцию создания преобразования, вторую часть
После диспетчеризации регистрации преобразования существует все еще несколько вещей, оставленных сделать в функции создания преобразования.
Во-первых, создайте экземпляр преобразования путем вызова SecTransformCreate
. Этот вызов заставляет код платформы вызывать Вашу функцию реализации (тот, который Вы зарегистрировали в Функции Создания Преобразования, Первой части), который поочередно создает фактическое преобразование. Функция создания преобразования в конечном счете возвращает получающийся экземпляр преобразования вызывающей стороне.
В этом примере вызов создания похож на это:
caesarCipher = SecTransformCreate(kCaesarCipher, error); |
Первый параметр является именем преобразования. Вторым является адрес a CFErrorRef
переменная, взятая от последнего параметра функции создания.
После создания экземпляра преобразования, если Ваша функция создания преобразования берет дополнительные параметры для ключей шифрования или других настроек преобразования, она должна установить надлежащие атрибуты на экземпляре преобразования путем вызова SecTransformSetAttribute
. (Пример шифра Цезаря не берет таких параметров.)
Наконец, функция создания преобразования возвращает экземпляр преобразования вызывающей стороне:
return caesarCipher; |
Обработка изменений атрибута
Для изменения поведения преобразования Вы устанавливаете атрибуты объекта преобразования. Шифр Цезаря преобразовывает, имеет единственную функцию создания, не передающую параметров преобразованию. Однако возможно иметь многократные функции создания, берущие дополнительные параметры для установки определенных атрибутов. Значения атрибута могут также быть установлены после того, как объект преобразования создается и может даже быть установлен другими преобразованиями.
За исключением атрибутов ввода и вывода, все атрибуты равны. Их данные могут быть любым Базовым объектом Основы (хотя они обычно CFDataRef
объекты). Платформа прозрачно обеспечивает управление потоком и полирующий для любых данных, отправленных в атрибут. Если входная очередь для атрибута достигает глубины десяти объектов, блоки отправителя (остановы), пока один или больше объектов не обрабатывается.
Входной атрибут является особенным тремя способами:
Входной атрибут принимает только
CFDataRef
объекты (к которому должна бросить вызывающая сторонаCFTypeRef
объекты).Когда входной атрибут преобразования установлен,
ProcessData
блок автоматически планируется. Этот механизм включает буферизацию и управление потоком.CFDataRef
объект, возвращающийся изProcessData
автоматически передается выходному атрибуту, по умолчанию тогда передающемуся вводу следующего преобразования в цепочке.Любые исходные данные, отправленные во входной атрибут, ставятся в очередь до
SecTransformExecute
вызывается.
Выходной атрибут является столь же особенным:
Выходной атрибут должен получить серию
CFDataRef
бросок объектов кCFTypeRef
объекты.Отправка a
CFErrorRef
объект илиNULL
к выводу указывает, что преобразование закончило обрабатывать данные.SecTransformExecute
требует, чтобы точно каждый преобразовал, имеют несвязанный выходной атрибут. Тот выходной атрибут обеспечивает, значение возвратилось к вызывающей стороне.
Обработка данных
ProcessData
действие выполняет большую часть работы в преобразовании.
Когда Вы обрабатываете данные, знать что CFDataRef
ввод может иметь любой размер. Таким образом Ваше преобразование должно быть в состоянии обработать любой размер данных, включая единственный байт или даже строку нулевой длины (который отличен от получения a NULL
указатель).
Если Ваш ProcessData
действие получает исходные данные, который слишком короток для обработки, оно должно буферизовать данные в преобразовании и возвратить строку нулевой длины. Это может тогда обработать те данные вместе с любыми будущими данными, как только преобразование получило достаточно байтов.
Если Ваш ProcessData
действие получает данные от любого атрибута (включая входной атрибут), но не готово обработать те данные из-за без вести пропавших входных данных от других атрибутов, это может вызвать SecTransformPushbackAttribute
продвигать данные назад на переднюю сторону входной очереди. Можно пододвинуть только единственный элемент данных обратно на атрибут. Когда любой другой атрибут предоставит данные, Вам подарят пододвинутые обратно данные снова. Несмотря на то, что Вы обычно пододвигали бы элемент данных обратно, Вы просто получили, это не требование; можно пододвинуть любой объект обратно любого типа CF.
Полный листинг кода
Полный листинг кода для шифра Цезаря показан в Полном Листинге кода. Можно использовать это в качестве начальной точки для изучения, как создать собственные преобразования.
Перечисление 5-1 шифр Цезаря (полный список)
/* |
* CaesarXform.c |
*/ |
#include <Security/SecCustomTransform.h> |
#include <Security/SecTransform.h> |
// ========================================================================= |
// Declaring the Transform Name |
// ========================================================================= |
/* This is the unique name for the custom transform type. */ |
const CFStringRef kCaesarCipher = CFSTR("com.apple.caesarcipher"); |
/* Name of the "key" attribute. */ |
const CFStringRef kKeyAttributeName = CFSTR("key"); |
/* Helper that returns a CFError. */ |
CFErrorRef invalid_input_error(void) |
{ |
return CFErrorCreate(kCFAllocatorDefault, kSecTransformErrorDomain, |
kSecTransformErrorInvalidInput, NULL); |
} |
// ========================================================================= |
// The Transform Implementation Function |
// ========================================================================= |
static SecTransformInstanceBlock CaesarImplementation(CFStringRef name, |
SecTransformRef newTransform, |
SecTransformImplementationRef ref) |
{ |
/* Instance Block: |
Every time a new instance of this custom transform class is |
created, this block is called. This behavior means that any |
block variables created in this block act like instance |
variables for the new custom transform instance. |
*/ |
SecTransformInstanceBlock instanceBlock = |
^{ |
CFErrorRef result = NULL; |
/* Data local to each instance: */ |
__block int _key = 0; |
/**************************************** |
* Action Blocks: * |
****************************************/ |
/* Key attribute action: |
This action is called when the key is set. |
*/ |
result = SecTransformSetAttributeAction( |
ref, |
kSecTransformActionAttributeNotification, |
kKeyAttributeName, |
^(SecTransformAttributeRef name, |
CFTypeRef d) { |
CFNumberGetValue( |
(CFNumberRef)d, |
kCFNumberIntType, |
&_key |
); |
return d; |
} |
); |
if (result) |
return result; |
/* Input attribute action: |
This action is called for each piece of |
data posted on the input attribute. It |
writes data to the output attribute. |
*/ |
result = SecTransformSetDataAction( |
ref, |
kSecTransformActionProcessData, |
^(CFTypeRef d) { |
if (NULL == d) // End of stream? |
return (CFTypeRef) NULL; // Just return a null. |
/* At this point, if desired, you |
can check whether the key is available |
and if not, you can call |
SecTransformPushbackAttribute. */ |
char *dataPtr = (char *)CFDataGetBytePtr((CFDataRef)d); |
CFIndex dataLength = CFDataGetLength((CFDataRef)d); |
// Do the processing in a buffer generated by |
// malloc for simplicity. |
char *buffer = (char *)malloc(dataLength); |
if (NULL == buffer) { |
// Return a CFErrorRef |
return (CFTypeRef) invalid_input_error(); |
} |
// Do the work of the Caesar cipher (Rot(n)) |
CFIndex i; |
for (i = 0; i < dataLength; i++) |
buffer[i] = dataPtr[i] + _key; |
return (CFTypeRef)CFDataCreateWithBytesNoCopy( |
NULL, |
(UInt8 *)buffer, |
dataLength, |
kCFAllocatorMalloc); |
} |
); |
return result; |
}; |
return Block_copy(instanceBlock); |
} |
// ========================================================================= |
// The Transform Creation Function |
// ========================================================================= |
SecTransformRef CaesarTransformCreate(CFIndex k, CFErrorRef* error) |
{ |
SecTransformRef caesarCipher; |
__block Boolean result = 1; |
static dispatch_once_t registeredOK = 0; |
dispatch_once(®isteredOK, |
^{ |
result = SecTransformRegister( |
kCaesarCipher, |
&CaesarImplementation, |
error); |
}); |
if (!result) |
return NULL; |
caesarCipher = SecTransformCreate(kCaesarCipher, error); |
if (NULL != caesarCipher) |
{ |
CFNumberRef keyNumber = CFNumberCreate(kCFAllocatorDefault, |
kCFNumberIntType, &k); |
SecTransformSetAttribute(caesarCipher, kKeyAttributeName, |
keyNumber, error); |
CFRelease(keyNumber); |
} |
return caesarCipher; |
} |
// The second function shows how to use custom transform defined in the |
// previous function |
// ========================================================================= |
// Testing the transform |
// ========================================================================= |
CFDataRef TestCaesar(CFDataRef theData, int rotNumber) |
{ |
CFDataRef result = NULL; |
CFErrorRef error = NULL; |
if (NULL == theData) |
return result; |
// Create an instance of the custom transform |
SecTransformRef caesarCipher = CaesarTransformCreate(rotNumber, &error); |
if (NULL == caesarCipher || NULL != error) |
return result; |
// Set the data to be transformed as the input to the custom transform |
SecTransformSetAttribute(caesarCipher, |
kSecTransformInputAttributeName, theData, &error); |
if (NULL != error) |
{ |
CFRelease(caesarCipher); |
return result; |
} |
// Execute the transform synchronously |
result = (CFDataRef)SecTransformExecute(caesarCipher, &error); |
CFRelease(caesarCipher); |
return result; |
} |
#include <CoreFoundation/CoreFoundation.h> |
int main (int argc, const char *argv[]) |
{ |
CFDataRef testData, testResult; |
UInt8 bytes[26]; |
int i; |
// Create some test data, a string from A-Z |
for (i = 0; i < sizeof(bytes); i++) |
bytes[i] = 'A' + i; |
testData = CFDataCreate(kCFAllocatorDefault, bytes, sizeof(bytes)); |
CFRetain(testData); |
CFShow(testData); |
// Encrypt the test data |
testResult = TestCaesar(testData, 3); |
CFShow(testResult); |
CFRelease(testData); |
CFRelease(testResult); |
return 0; |
} |