Продолжаю публикацию на Хабре глав из своей статьи "ASN.1 простыми словами". Предыдущая часть может быть найдена по адресу ASN.1 простыми словами (кодирование типа REAL).
Общее описание типа:
Сам по себе OBJECT IDENTIFIER представляет собой набор целых без знаковых чисел, разделенных знаком ".". Примеры возможных OBJECT IDENTIFIER: 0.1.1, 1.1.1, 2.1234.1234.1234.1234.
Кодирование OBJECT IDENTIFIER (OID) состоит из последовательного кодирования всех составляющих данный OID без знаковых целых (SID — sub identifier).
Для уменьшения общего размера закодированного OID к первым двум SID применяются следующие правила:
Примеры кодирования первых двух октетов:
Каждый SID кодируется не зависимо от остальных. Все SID кодируются друг за другом без дополнительных разделителей. Правила кодирования SID одинаковы для всех SID кроме первых двух (см. выше).
Нужно отметить, что теоретически каждый SID может иметь сколько угодно большое целое значение. Следовательно каждый отдельный SID может кодироваться произвольным, сколь угодно большим, количеством октетов. Обычно для кодирования переменного количества октетов применяется дополнительное кодирование количества этих октетов (перед последовательностью закодированных октетов кодируется октет, содержащий количество закодированных октетов). Однако в случае кодирования SID применяется другой подход — самый старший бит каждого из октетов, кодирующих отдельный SID, является своеобразным флагом, по которому можно судить последний ли это октет для данного SID или нет. То есть так как один SID может быть закодирован с помощью более чем одного октета (в случае большого целого числа), то применяется следующее правило: во всех октетах, кодирующих значение SID, кроме последнего (младшего) октета значение самого старшего бита должно быть установлено в 1. Таким образом получается, что значащими для кодирования SID битами в 8-ми битовом октете являются только младшие 7 бит, старший бит в каждом октете используется как флаг, указывающий на последний октет. В связи с этим перед кодированием каждый SID переводится из десятичной формы в форму разложения по основанию 128 (группируется по 7 бит).
Примеры кодирования SID:
При кодировании SID должно использоваться минимальное количество октетов. То есть SID = 643 не должен быть представлен как ( 0*1282 + 5*1281 + 3*1280 ) = ( 00 05 03 )256 и, следовательно, не должен быть закодирован как ( 80 85 03 )256. Простейшая проверка на правильность кодирования SID — первый октет закодированного SID не должен быть равен 80256.
Общее описание типа:
Вопросы кодирования целых (INTEGER) уже рассматривался ранее (в главе про кодирование типа REAL). Однако еще раз напомним основные особенности этого кодирования.
В ASN.1 могут быть закодированы как положительные, так и отрицательные числа. Каждое целое число практически не ограничено в своей величине (то есть в ASN.1 могут быть закодированы сколь угодно большие (по модулю) целые числа).
Каждое целое число кодируется последовательностью октетов, каждый октет представляет собой 8 бит информации. Каждый октет представляет собой «вес» перед соответствующей степенью числа 256, участвующий в разложении кодируемого числа по основанию 256. То есть для кодирования числа исходное число сначала разлагается по основанию 256, и затем значения «весов» перед соответствующими степенями 256 кодируются в качестве октетов. Например число 8388607 будет кодироваться по следующей схеме:
Кодирование отрицательных целых значений осуществляется по отдельным правилам. Фактически в закодированном отрицательном целом хранится не одно, а два целых числа: основное число и число, которое нужно вычесть из основного числа, чтобы при декодировании получить изначально закодированное отрицательное число. То есть при декодировании изначальное отрицательное число получается по формуле: N = X — Y, где X — основное число, а Y — вычитаемое число. Не трудно понять, что для того, чтобы N получилось отрицательным, необходимо чтобы выполнялось условие Y > X.
Подробные правила формирования как основного, так и вычитаемого числа уже были описаны ранее, поэтому отсылаю читателя к главе о кодировании типа REAL.
Примеры кодирования целых чисел:
Фактически к кодируемому целому числу предъявляется требование: старшие 9 бит закодированного числа не должны все быть равны 1, и не должны все быть равны 0 (старшие 9 бит закодированного числа не должны быть равны между собой). Если первые 9 бит закодированного числа равны 0, то значит без ущерба для закодированного значения старший октет (старшие 8 бит) можно отбросить (при декодировании он бы просто добавил слагаемое 0*256n, что никак не повлияет на значение числа). Если же старшие 9 бит закодированного числа равны 1 то закодированное число отрицательное и может быть перекодировано с применением меньшего числа октетов (при кодировании были добавлены лишние нулевые октеты в вычитаемое число).
Пример добавления лишних нулевых октетов в вычитаемое число. Возьмем для кодирования число (-128). Положим вычитаемое число будет равно: (80 00)256 = 3276810, и основное значение вычисляется после решения уравнения ( x — 32768 ) = -128. Следовательно x = 3264010 = ( 7F 80 )256. Устанавливая старший бит получаем окончательное кодирование (FF 80)256 (здесь равны между собой 9 старших бит). Но ранее мы уже кодировали число (-128) и знаем, что кодирование этого числа в ASN.1 может быть представлено в более короткой форме — в виде только одного октета (80)256;
Ну и в конце этой главы ещё раз напомним читателю, что в современных компьютерных системах кодирование целых чисел уже (автоматически) осуществляется по выше описанным правилам. Правда с одной оговоркой: для кодирования отрицательных чисел все-таки применяется «добавление лишних нулевых октетов в вычитаемое число» (см. предыдущий параграф). То есть если количество октетов (байт) для хранения целого числа составляет 4, то число (-128) будет закодировано в виде FF FF FF 80 (то есть при кодировании было применено «вычитаемое число» равное (80 00 00 00)256 = 2147483648).
В ASN.1 применяется кодирование для достаточно широкого набора строковых типов. Вот их полный перечень:
Некоторые из них уже являются устаревшими и не применяемыми типами строк (например VideotexString). Каждый тип строк описывает некий набор символов, которые можно применять в строках с данным типом. Дополнительно в используемых строках также могут применяться так называемые «управляющие последовательности», позволяющие автоматически настраивать обработку отдельно выделенной строки на поддерживающих эту возможность терминалах. Тот, кто программировал на С\С++ наверняка часто использовал «управляющую последовательность» \n — перевод строки. На самом деле управляющих последовательностей очень много уже в стандарте, кроме того можно создавать собственные управляющие последовательности, которые будут обрабатываться только на специализированных терминалах (например переключать цвет выделенных строк на красный, подчеркивать строки и т.п.).
Наиболее продвинутыми и современными форматами являются форматы строк, основанные на стандарте Unicode. Основу стандарта составляет документ ISO 10646.
Тип UniversalString (класс тэга UNIVERSAL, номер тэга 28, форма кодирования — примитивная). Кодирует Unicode строки, где каждый символ кодируется 4-мя байтами (октетами).
Тип BMPString (класс тэга UNIVERSAL, номер тэга 30, форма кодирования — примитивная). Подмножество символов Unicode, каждый символ кодируется всегда 2-мя байтами (октетами).
Тип UTF8String (класс тэга UNIVERSAL, номер тэга 12, форма кодирования — примитивная). Представление символов Unicode, но с дополнительной обработкой, позволяющей кодировать каждый символ в последовательность байт переменной длины (от 1 до 7-ми байт).
Все типы описывающие дату и время в ASN.1 являются обыкновенными UTF-8 строками, где значения для года, месяца и так далее кодируется в определенном формате. Форматы для представления каждого типа описываются в свободно доступном стандарте ISO 8601.
Тип UTCTime. Простейший временной тип. Может кодировать только дату и UTC (Universal Coordinated Time) время. Формат строки, кодирующей UTCTime, может иметь либо вид YYMMDDHHMMSSZ (где YY — две последние цифры года, MM — две цифры месяца, DD — две цифры дня, HH — две цифры часа (формат 24-х часовой), MM — две цифры минут, SS — две цифры секунд), либо может также кодировать UTC время вместе с относительным смещением для соответствующего часового пояса в виде YYMMDDHHMMSS+hhmm (или YYMMDDHHMMSS-hhmm).
Тип GeneralizedTime. Расширение типа UTCTime, использующее уже 4 цифры для обозначения года, плюс тип GeneralizedTime позволяет использовать дробные значения для любой из временных составляющих (часа, минуты или секунды). Соответственно форматы могут быть следующими:
Все описанные ниже типы являются новыми для последней версии стандарта X.680:2008.
Тип TIME. Описывает только время в формата HH:MM:SS, либо в формате HHMMSS.
Тип TIME-OF-DAY. То же самое, что и тип TIME, за исключением того, что формат может быть только HHMMSS.
Тип DATE. Описывает только дату в формате YYYYMMDD.
Тип DATE-TIME. Описывает как дату, так и время для этой даты. Формат представления YYYYMMDDHHMMSS. В случае если значение крайних фракций времени равно 0 (то есть например 0 секунд), то нулевое значение может быть исключено (строка сокращена).
Тип DURATION. Описывает разность между двумя временными промежутками. Формат представления nnYnnMnnDTnnHnnMnnS.
Опубликована заключительная часть моей статьи ASN.1 простыми словами (часть 3, заключительная).
Глава 3. Кодирование типа OBJECT IDENTIFIER
Общее описание типа:
- Класс тэга — UNIVERSAL (00);
- Номер тэга — 6;
- Форма кодирования значения — примитивная (не конструктивная форма);
Сам по себе OBJECT IDENTIFIER представляет собой набор целых без знаковых чисел, разделенных знаком ".". Примеры возможных OBJECT IDENTIFIER: 0.1.1, 1.1.1, 2.1234.1234.1234.1234.
Кодирование OBJECT IDENTIFIER (OID) состоит из последовательного кодирования всех составляющих данный OID без знаковых целых (SID — sub identifier).
Для уменьшения общего размера закодированного OID к первым двум SID применяются следующие правила:
- Первое без знаковое целое (SID1) должно быть или 0, или 1, или 2. Другие значения недопустимы. SID1 исключается из процесса кодирования, вместо этого значение SID1 вычисляется естественным путём при декодировании SID2 (см. ниже);
- Перед кодированием второго SID к нему применяется следующая формула: SID2 = SID1*40 + SID2;
- Для гарантированного определения SID1 из результата полученного выражения для SID2 к SID2 предъявляются следующие требования:
- Если SID1 = 0 или SID1 = 1 то значение SID2 должно лежать в диапазоне от 0 (включительно) до 39 (включительно);
- Если SID1 = 2, то SID2 может выражаться произвольным целым без знаковым числом;
Примеры кодирования первых двух октетов:
- «0.39» кодируется одним целым числом 0*40 + 39 = 39;
- «1.0» кодируется одним целым числом 1*40 + 0 = 40;
- «1.39» кодируется одним целым числом 1*40 + 39 = 79;
- «2.0» кодируется одним целым числом 2*40 + 0 = 80;
- «2.39» кодируется одним целым числом 2*40 + 39 = 119;
- «2.339» кодируется одним целым числом 2*40 + 339 = 419;
Каждый SID кодируется не зависимо от остальных. Все SID кодируются друг за другом без дополнительных разделителей. Правила кодирования SID одинаковы для всех SID кроме первых двух (см. выше).
Нужно отметить, что теоретически каждый SID может иметь сколько угодно большое целое значение. Следовательно каждый отдельный SID может кодироваться произвольным, сколь угодно большим, количеством октетов. Обычно для кодирования переменного количества октетов применяется дополнительное кодирование количества этих октетов (перед последовательностью закодированных октетов кодируется октет, содержащий количество закодированных октетов). Однако в случае кодирования SID применяется другой подход — самый старший бит каждого из октетов, кодирующих отдельный SID, является своеобразным флагом, по которому можно судить последний ли это октет для данного SID или нет. То есть так как один SID может быть закодирован с помощью более чем одного октета (в случае большого целого числа), то применяется следующее правило: во всех октетах, кодирующих значение SID, кроме последнего (младшего) октета значение самого старшего бита должно быть установлено в 1. Таким образом получается, что значащими для кодирования SID битами в 8-ми битовом октете являются только младшие 7 бит, старший бит в каждом октете используется как флаг, указывающий на последний октет. В связи с этим перед кодированием каждый SID переводится из десятичной формы в форму разложения по основанию 128 (группируется по 7 бит).
Примеры кодирования SID:
- SID = 64310 = 5*1281 + 3*1280 = ( 05 03 )128. Так как все октеты, кроме самого младшего, должны иметь установленный старший бит, то SID равный 643 кодируется в ASN.1 с помощью двух октетов ( 85 03 )256;
- SID = 11354910 = 6*1282 + 119*1281 + 13*1280 = ( 06 77 0D ) 128. Так как все октеты, кроме самого младшего, должны иметь установленный старший бит, то SID равный 113549 кодируется в ASN.1 с помощью трех октетов ( 86 F7 0D )256;
- SID = 4915210 = 3*1282. В этом случае в разложении отсутствуют младшие степени числа 128. Но так как при кодировании SID нигде не сохраняется значение экспоненты числа, то для нужд кодирования 49152 должно представляться в виде ( 3*1282 + 0*1281 + 0*1280 ) = ( 03 00 00 )128 (то есть закодированное число должно всегда состоять из числа октетов больших на 1 старшей степени числа 128 при разложении). Так как все октеты, кроме самого младшего, должны иметь установленный старший бит, то SID равный 49152 кодируется в ASN.1 с помощью трех октетов ( 83 80 00 )256;
При кодировании SID должно использоваться минимальное количество октетов. То есть SID = 643 не должен быть представлен как ( 0*1282 + 5*1281 + 3*1280 ) = ( 00 05 03 )256 и, следовательно, не должен быть закодирован как ( 80 85 03 )256. Простейшая проверка на правильность кодирования SID — первый октет закодированного SID не должен быть равен 80256.
Глава 4. Кодирование типа INTEGER
Общее описание типа:
- Класс тэга — UNIVERSAL (00);
- Номер тэга — 2;
- Форма кодирования значения — примитивная (не конструктивная форма);
Вопросы кодирования целых (INTEGER) уже рассматривался ранее (в главе про кодирование типа REAL). Однако еще раз напомним основные особенности этого кодирования.
В ASN.1 могут быть закодированы как положительные, так и отрицательные числа. Каждое целое число практически не ограничено в своей величине (то есть в ASN.1 могут быть закодированы сколь угодно большие (по модулю) целые числа).
Каждое целое число кодируется последовательностью октетов, каждый октет представляет собой 8 бит информации. Каждый октет представляет собой «вес» перед соответствующей степенью числа 256, участвующий в разложении кодируемого числа по основанию 256. То есть для кодирования числа исходное число сначала разлагается по основанию 256, и затем значения «весов» перед соответствующими степенями 256 кодируются в качестве октетов. Например число 8388607 будет кодироваться по следующей схеме:
- Разложим число по основанию 256: 838860710 = 127*2562 + 255*2561 + 255*2560;
- Получаем, что «веса» при соответствующих степенях 256 будут: 127, 255 и 255;
- Переводим каждое число в последовательность бит, а затем кодируем эту последовательность бит, группируя в группы по 4 бита. Получаем следующие значения для «весов»: 7F FF FF;
Кодирование отрицательных целых значений осуществляется по отдельным правилам. Фактически в закодированном отрицательном целом хранится не одно, а два целых числа: основное число и число, которое нужно вычесть из основного числа, чтобы при декодировании получить изначально закодированное отрицательное число. То есть при декодировании изначальное отрицательное число получается по формуле: N = X — Y, где X — основное число, а Y — вычитаемое число. Не трудно понять, что для того, чтобы N получилось отрицательным, необходимо чтобы выполнялось условие Y > X.
Подробные правила формирования как основного, так и вычитаемого числа уже были описаны ранее, поэтому отсылаю читателя к главе о кодировании типа REAL.
Примеры кодирования целых чисел:
- Пусть имеем уже закодированное число 80256 = 128*2560 = 12810 = (1000 0000)2. Здесь основное число образуется битами 1-7 (младшими, крайними справа) и равно 0, вычитаемое образуется путём маскирования всех битов, кроме старшего и равно ( 1000 0000)2 = 80256 = 12810. Следовательно закодированное число равно 0 — 128 = -128;
- Кодирование положительного значения +128 осуществляется с помощью последовательность октетов 0*2561 + 128*2560 = ( 00 80 )256, то есть добавляется старший нулевой октет. Фактически здесь тоже можно вычислить как основное число, так и вычитаемое число: основное число будет равно 128, а вычитаемое будет равно 0;
- Закодируем число -13610. Число 13610 = 88256 = ( 1000 1000 )2. Так как в этом числе уже установлен старший бит то для правильного кодирования вычитаемое число должно быть образовано двумя октетами и быть равным (80 00)256 = 128*2561 + 0*2560 = 3276810. Следовательно основное число при кодировании (-136) может быть получено после решения следующего уравнения: х — 32768 = -136. То есть получаем значение «х» равным 32626 = 127*2561 + 120*2560 = ( 7F 78 )256. Устанавливая для этого числа старший бит получаем что окончательное кодирование для числа (-136) будет осуществляться двумя октетами ( FF 78 )256;
Фактически к кодируемому целому числу предъявляется требование: старшие 9 бит закодированного числа не должны все быть равны 1, и не должны все быть равны 0 (старшие 9 бит закодированного числа не должны быть равны между собой). Если первые 9 бит закодированного числа равны 0, то значит без ущерба для закодированного значения старший октет (старшие 8 бит) можно отбросить (при декодировании он бы просто добавил слагаемое 0*256n, что никак не повлияет на значение числа). Если же старшие 9 бит закодированного числа равны 1 то закодированное число отрицательное и может быть перекодировано с применением меньшего числа октетов (при кодировании были добавлены лишние нулевые октеты в вычитаемое число).
Пример добавления лишних нулевых октетов в вычитаемое число. Возьмем для кодирования число (-128). Положим вычитаемое число будет равно: (80 00)256 = 3276810, и основное значение вычисляется после решения уравнения ( x — 32768 ) = -128. Следовательно x = 3264010 = ( 7F 80 )256. Устанавливая старший бит получаем окончательное кодирование (FF 80)256 (здесь равны между собой 9 старших бит). Но ранее мы уже кодировали число (-128) и знаем, что кодирование этого числа в ASN.1 может быть представлено в более короткой форме — в виде только одного октета (80)256;
Ну и в конце этой главы ещё раз напомним читателю, что в современных компьютерных системах кодирование целых чисел уже (автоматически) осуществляется по выше описанным правилам. Правда с одной оговоркой: для кодирования отрицательных чисел все-таки применяется «добавление лишних нулевых октетов в вычитаемое число» (см. предыдущий параграф). То есть если количество октетов (байт) для хранения целого числа составляет 4, то число (-128) будет закодировано в виде FF FF FF 80 (то есть при кодировании было применено «вычитаемое число» равное (80 00 00 00)256 = 2147483648).
Глава 5. Кодирование строковых значений
В ASN.1 применяется кодирование для достаточно широкого набора строковых типов. Вот их полный перечень:
- NumericString;
- PrintableString;
- TeletextString (тип полностью совпадает с T61String);
- VideotexString;
- VisibleString;
- IA5String;
- GraphicString;
- GeneralString;
- UniversalString;
- BMPString;
- UTF8String;
Некоторые из них уже являются устаревшими и не применяемыми типами строк (например VideotexString). Каждый тип строк описывает некий набор символов, которые можно применять в строках с данным типом. Дополнительно в используемых строках также могут применяться так называемые «управляющие последовательности», позволяющие автоматически настраивать обработку отдельно выделенной строки на поддерживающих эту возможность терминалах. Тот, кто программировал на С\С++ наверняка часто использовал «управляющую последовательность» \n — перевод строки. На самом деле управляющих последовательностей очень много уже в стандарте, кроме того можно создавать собственные управляющие последовательности, которые будут обрабатываться только на специализированных терминалах (например переключать цвет выделенных строк на красный, подчеркивать строки и т.п.).
Наиболее продвинутыми и современными форматами являются форматы строк, основанные на стандарте Unicode. Основу стандарта составляет документ ISO 10646.
Тип UniversalString (класс тэга UNIVERSAL, номер тэга 28, форма кодирования — примитивная). Кодирует Unicode строки, где каждый символ кодируется 4-мя байтами (октетами).
Тип BMPString (класс тэга UNIVERSAL, номер тэга 30, форма кодирования — примитивная). Подмножество символов Unicode, каждый символ кодируется всегда 2-мя байтами (октетами).
Тип UTF8String (класс тэга UNIVERSAL, номер тэга 12, форма кодирования — примитивная). Представление символов Unicode, но с дополнительной обработкой, позволяющей кодировать каждый символ в последовательность байт переменной длины (от 1 до 7-ми байт).
Глава 6. Кодирование даты и времени
Все типы описывающие дату и время в ASN.1 являются обыкновенными UTF-8 строками, где значения для года, месяца и так далее кодируется в определенном формате. Форматы для представления каждого типа описываются в свободно доступном стандарте ISO 8601.
Тип UTCTime. Простейший временной тип. Может кодировать только дату и UTC (Universal Coordinated Time) время. Формат строки, кодирующей UTCTime, может иметь либо вид YYMMDDHHMMSSZ (где YY — две последние цифры года, MM — две цифры месяца, DD — две цифры дня, HH — две цифры часа (формат 24-х часовой), MM — две цифры минут, SS — две цифры секунд), либо может также кодировать UTC время вместе с относительным смещением для соответствующего часового пояса в виде YYMMDDHHMMSS+hhmm (или YYMMDDHHMMSS-hhmm).
Тип GeneralizedTime. Расширение типа UTCTime, использующее уже 4 цифры для обозначения года, плюс тип GeneralizedTime позволяет использовать дробные значения для любой из временных составляющих (часа, минуты или секунды). Соответственно форматы могут быть следующими:
- YYYYMMDDHHMMSSZ;
- YYYYMMDDHHMMSS+hhmm;
- YYYYMMDDHHMMSS-hhmm;
- YYYYMMDDHHMMSS.nn (количество знаков после точки не ограничено);
- YYYYMMDDHHMM.nn (количество знаков после точки не ограничено);
- YYYYMMDDHH.nn (количество знаков после точки не ограничено);
Все описанные ниже типы являются новыми для последней версии стандарта X.680:2008.
Тип TIME. Описывает только время в формата HH:MM:SS, либо в формате HHMMSS.
Тип TIME-OF-DAY. То же самое, что и тип TIME, за исключением того, что формат может быть только HHMMSS.
Тип DATE. Описывает только дату в формате YYYYMMDD.
Тип DATE-TIME. Описывает как дату, так и время для этой даты. Формат представления YYYYMMDDHHMMSS. В случае если значение крайних фракций времени равно 0 (то есть например 0 секунд), то нулевое значение может быть исключено (строка сокращена).
Тип DURATION. Описывает разность между двумя временными промежутками. Формат представления nnYnnMnnDTnnHnnMnnS.
UPDATE
Опубликована заключительная часть моей статьи ASN.1 простыми словами (часть 3, заключительная).