ASN.1 (Abstract Syntax Notation One) представляет из себя язык для описания структур данных. По сути — это набор правил, для преобразования значений определённого типа в поток байтов для их последующей отправки по каналу связи. По мере развития языка, улучшались методы кодирования. Так помимо методов определения структур данных ASN.1 появились так же различные методы кодирования (BER, CER, DER, PER и др.). Одним из самых компактных методов кодирования ASN.1 (в плане результирующей байтовой последовательности), является «Packed Encoding Rules (PER)». Дело в том, что типы в BER (CER, DER), кодируются в виде последовательности TLV (Tag Length Value), в то время как в PER используются последовательности V (Value) или LV (Length Value). Такой подход позволяет уменьшить закодированную последовательность, однако, если BER можно декодировать без доступа к источнику ASN.1, получив «голые» результирующие данные (тип данных, плюс значение), то с PER такой фокус не пройдёт, без источника, какие-либо осмысленные результаты получить не получится (я не рассматриваю вариант глубокого анализа, с набором статистики по большому количеству реализаций).
В этой статье, я хотел бы рассказать о принципах кодирования структур данных при использовании правил кодирования PER.
ASN.1 PER, благодаря своей компактности, нашёл широкое применение в сетях сотовой связи, начиная с сетей 3-го поколения. Дело в том, что использование ASN.1 позволяет унифицировать структуры команд, не увеличивая (а иногда даже и уменьшая) при этом размер закодированных данных. Вот так, например выглядит «Location Area Identification» в описании «Mobile radio interface Layer 3 specification», разработанном ещё для сетей GSM:
А вот так, тот же параметр из «S1 Application Protocol», в описании ASN.1:
LAI ::= SEQUENCE { pLMNidentityPLMNidentity, lAC LAC, iE-Extensions ProtocolExtensionContainer { {LAI-ExtIEs} } OPTIONAL, ... } LAI-ExtIEs S1AP-PROTOCOL-EXTENSION ::= { ... } LAC ::= OCTET STRING (SIZE (2)) PLMNidentity ::= TBCD-STRING TBCD-STRING ::= OCTET STRING (SIZE (3))
Кстати если во втором случае, за возможность расширения выступает опциональное поле (iE-Extensions) и бит расширения (…), то в первом такую возможность даёт зарезервированный 8-й бит первого октета (правда первый октет является опциональным и при необходимости расширения, придётся переработать спецификацию, что не очень хорошо).
В результате и первый и второй вариант, после кодирования, дадут нам байтовую последовательность длиной 6 байт. Конечно при встраивании этого элемента в структуру команды, в случае с ASN.1, придётся добавлять его с помощью класса, что добавит как минимум байт идентификатора и байт длины, но в этом случае «игра стоит свеч».
Плюсом использования ASN.1 является некая универсальность, для создания обработчика требуется лишь компилятор ASN.1. Минусом, по моему сугубо личному мнению, является его сложность в понимании (всё-таки наглядное представление битовых последовательностей, читается гораздо проще).
За описание методов кодирования PER, отвечает стандарт «ITU-T Rec. X.691». Что бы лучше понять систему кодирования, каждое правило я буду сопровождать примером, с использованием результатов в виде битовых последовательностей. Я не буду вдаваться в подробности синтаксиса самого ASN.1, всё-таки эта статья посвящена правилам PER кодирования. Подразумевается, что синтаксис ASN.1, вам известен. Если же нет, вы можете почитать стандарт «ITU-T Rec. X.680», хотя признаюсь, что это то ещё удовольствие, или же воспользоваться руководством по выживанию ASN.1 (https://pro-ldap.ru/tr/zytrax/tech/asn1.html).
В конце данной статьи, для закрепления полученной информации, я приведу пример декодирования одной из команд NBAP протокола, взятой из «wireshark wiki».
Итак, предлагаю налить себе чаю и начинать
Первое о чём хотелось бы сказать, это то, что существует два формата кодирования: «UNALIGNED» и «ALIGNED». В первом случае все значения размещаются строго друг за другом (в конце добавляются нулевые биты до кратности байту). Во втором всё гораздо хуже. Если значение длинной меньше октета (или двух, в зависимости от типа), то оно размещается следом за предыдущем без добавления бит выравнивания (padding bits), в противном случае, к началу закодированного значения добавляются нулевые биты (0..7), выравнивающие битовое поле на начало октета.
Здесь наверное нужен пример.
Возьмём следующее сообщение и закодируем двумя способами.
Message ::= SEQUENCE{ field1 BITSTRING(4) ::=7 field2 BITSTRING(7) ::= 15 field3 INTEGER(0..255) ::=25 }
«UNALIGNED» вариант:
«ALIGNED» вариант:
Далее поговорим об ограничениях и длинах. Типы, используемые для кодирования чисел, строк и последовательностей одного типа данных, могут иметь фиксированный размер, ограничения по длине, либо могут быть безграничными (как власть тёмного владыки).
Например, нам необходимо закодировать двух байтовый идентификатор (пусть это будет udp порт). Для этого мы можем использовать тип «OCTET STRING», фиксированного размера 2 октета. В записи ASN.1 это будет выглядеть следующим образом — OCTET STRING (SIZE (2)).
Но, что делать, если кодируемое значение может, занимать как один октет в одном случае, так десять, в другом. Тогда вводится понятие ограниченного размера. Записывается это следующим образом: OCTET STRING (SIZE (0..15)). В этом случае, потребуется дополнительное поле, указывающее текущий размер закодированного значения. Всегда кодируется значение, лежащее в диапазоне между минимумом и максимумом. То есть, если у вас указан диапазон 10…23, а значение равно 15, то оно будет закодировано значением равным 5 (10 + 5 = 15).
Здесь необходимо сделать дополнительное уточнение, поле, указывающее текущий размер закодированного значения будет добавляться не во всех случаях. Например для типа INTEGER, параметр «SIZE» будет определять размер битового поля, требуемого для кодируемого значения. Размер поля для кодирования значения, выбирается согласно правилам кодирования ограниченных целых чисел (constrained whole number).
Правилам кодирования ограниченных целых чисел.
Обозначим минимальное значение как «lb», а максимальное, как «ub». Тогда диапазон кодируемых значений равен (ub – lb + 1). Допустим нам нужно закодировать значение «n», тогда закодированное число будет равно (n – lb).
Размер поля для кодирования значения будет выбираться исходя из диапазона кодируемых значений:
-
Диапазон значений не превышает 255 (случай битового поля)
В этом случае поле занимает минимальное количество бит требуемое для кодирования значения.
Диапазон
Количество бит
1
1
2, 3
2
4, 5, 6, 7
3
8…15
4
16…31
5
32…63
6
64…127
7
128…255
8
-
Диапазон значений равен 256 (случай одного октета)
В этом случае поле занимает один октет (для «ALIGNED» формата добавляются биты выравнивания).
-
Диапазон значений больше чем 256 и меньше, либо равен 65535 (случай двух октетов).
В этом случае поле занимает два октета (для «ALIGNED» формата, при необходимости, добавляются биты выравнивания).
-
Диапазон значений больше чем 65535 (Случай не фиксированной длины).
В этом варианте, значение будет закодировано минимально необходимым числом октетов, с добавлением поля длины, указывающем число октетов (для «ALIGNED» формата, при необходимости, добавляются биты выравнивания). Для ограниченных значений, поле длины будет занимать минимальное число бит, начиная со значения = 0, соответствующего длине в 1 октет, для неограниченных, поле длины кодируется в соответствии с общими правилами кодирования длин.
Приведём пример, используя тип «INTEGER».
INTEGER(0..7) ::= 5
Значение 0 кодируется как 0, 1 как 1 и так далее.
INTEGER(10..22) ::= 15
Значение 10 кодируется как 0, 11 как 1 и так далее.
INTEGER(0..255) ::= 5
Значение кодируется в один октет.
INTEGER(0..16777215) ::= 5
Значение кодируется минимальным числом октетов, с добавлением поля длины. Максимальное значение может занимать 3 октета, поэтому длина может принимать значения 0 (1 октет), 1 (2 октета) и 2 (3 октета) и занимает 2 бита.
И, немного забегая вперёд:
INTEGER(MIN..22) ::= 5 или INTEGER ::= 5
Значение кодируется в минимальное число октетов, с добавление поля длины. При это поле длины не фиксировано, и кодируется согласно общим правилам кодирования длин.
Общие правила кодирования длин.
В последнем примере, у нас либо присутствует только максимальное ограничение, либо отсутствуют ограничения вовсе. В этом случае используется вариант не фиксированной длины. Тогда поле длины формируется исходя из трёх правил.
-
Длина принимает значения 0…127.
В этом случае поле длины занимает один октет, при этом начальный бит всегда равен нулю, остальные биты кодируют значение.
7
6
5
4
3
2
1
0
0
Значение = 7 бит
-
Длина принимает значения 128…16383.
В этом случае поле длины занимает два октета, при этом начальный бит первого октета равен 1, а следующий за ним бит равен 0, остальные биты кодируют значение.
7
6
5
4
3
2
1
0
1
0
Значение = 6 бит
Значение (продолжение) = 8 бит
-
Длина превышает значение 16383.
В этом случае используется фрагментация. Вначале идут фрагменты размером 16К, 32К, 48К или 64К. Длина фрагмента занимает один октет, два начальных бита которого равны 1, а остальные 6 бит указывают количество фрагментов размером 16К. Количество фрагментов должно лежать в пределах 1…4. Последний фрагмент (в случае когда общая длина кодируемого сообщения не кратна 16К) использует длины описанные выше. В качестве примера, рассмотри сообщение состоящее из 144К + 1 элементов (64К + 64К + 16К + 1).
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
64К элементов |
|||||||
1 |
1 |
0 |
0 |
0 |
1 |
0 |
0 |
64К элементов |
|||||||
1 |
1 |
0 |
0 |
0 |
0 |
0 |
1 |
16К элементов |
|||||||
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 элемент |
Помимо всего вышесказанного, хочу упомянуть тут так же о правилах кодирования небольших неотрицательных целых чисел (normally small non-negative whole number). Если значение не превышает 63, то оно кодируется при помощи 7 бит, при этом начальный бит равен 0, а оставшиеся 6 кодируют значение. В остальных случаях начальный бит ставится в 1, после чего добавляется значение, закодированное по общему правилу кодирования длин. Более подробно, данное правило будет описано в описании типа «CHOICE».
Длины всегда используются в следующих типах:
-
полуограниченные типы (SIZE (0…MAX));
-
неограниченные типы (SIZE(MIN…MAX));
-
типы с расширением, когда значение выходит за рамки основного диапазона (SIZE(5,…)).
Значения длин соответствуют:
-
Битам, для типа «BIT STRING»;
-
Символам, для строковых типов с известным множителем бит на символ;
-
Итерациям , для типов «SEQUENCE OF» и «SET OF»;
-
Октетам, во всех остальных случаях;
В «ALIGNED» варианте, значения закодированные по общему правилу кодирования длин, всегда выравнены на начало октета.
Ну что ж, разобравшись с основами кодирования (я надеюсь было более менее понятно), перейдём к описанию основных типов данных. Скажу сразу, я не буду рассматривать все возможные типы данных, возьму только наиболее часто используемые. В ASN.1 существуют 3 класса типов: базовые типы, строковые типы и сконструированные типы.
Базовые типы
NULL type:
Пустой тип. Кодируется битовым полем нулевой длины (по факту отсутствует).
BOOLEAN type:
Тип кодируется однобитным полем без добавления длины. Значение 1 соответствует «true», 0 — «false».
INTEGER type:
Для «ALIGNED» варианта:
-
Значения, укладывающиеся в диапазон <=255, кодируются с использованием битового поля минимальной длины, без добавления поля длины.
value ::=INTEGER(0..7) ::= 5 – занимает поле длиной 3 бита
При добавление бита extention:
value ::= INTEGER(0..7, …) ::= 5
-
Значения, укладывающиеся в диапазон >=256 но не превышающие 64K, кодируются в необходимое число октетов с добавлением бит выравнивания (при необходимости выравнивания).
value ::= INTEGER(0..255, …) ::= 5
-
Значения превышающие 2 октета, кодируются в минимально-необходимое число октетов, с добавлением поля длины. Поле длины занимает минимальное число бит, необходимое для кодирования. Если значение не ограниченно (верхняя или нижняя граница не определена), и может быть закодировано в последовательность длиной не превышающей 127 октетов, то поле длины составляет один октет.
value ::= INTEGER(0..16777215) ::= 65536
value ::= INTEGER(1..MAX, …) ::= 1023 (кодируется как 1022, так как минимальное значение = 1)
-
Если extension бит = 1, значения кодируются в минимальное число октетов, с добавлением поля длины, аналогично предыдущему пункту.
value ::= INTEGER(0..15, …) ::= 20
Для «UNALIGNED» варианта:
-
В этом случае значения кодируются наименьшим количеством бит, необходимых для их представления. В случае неограниченных значений, используется правило для «ALIGNED» формата (без бит выравнивания).
value ::= INTEGER(0..255, …) ::= 5
ENUMERATED type:
-
Значения кодируются в соответствии с порядковым номером от 0 и выше. Размер, зависит от количества значений входящих в тип.
-
Кодируется по правилам INTEGER ограниченной длины. При наличии расширения добавляется extension бит.
-
Когда extension бит = 1, кодируется согласно правилам неограниченной длины.
value ::= ENUMERATED{ first, second, third, ... } ::= second
BIT STRING и OCTET STRING type:
Я объединил эти 2 типа в один, так как кодируются они одинаково. Отличие заключается только в кодировании длины (если размер типа не определён). Для BIT STRING длина указывается в битах, для OCTET STRING в октетах.
Для «ALIGNED» варианта:
-
Значения укладывающиеся в 16 бит (для BIT STRING) или в 2 октета для (OCTET STRING) кодируются с использованием битового поля минимальной длины.
-
Начиная с длин, равных 16 битам (или 2 октетам), требуется добавление бит выравнивания.
-
Для значений фиксированной длины, поле длины не добавляется.
value ::=BIT STRING(SIZE(16), …) ::= 0b1010101010101010
value ::=OCTET STRING(SIZE(2), …) ::= 0xAAAA
-
Если длина не превышает значения 64K, поле длины кодируется как ограниченное целочисленное число (занимая размер от 1 бита до 2х октетов) и указывает количество бит (для BIT STRING) или количество октетов (для OCTET STRING), входящих в значение.
-
Для значений длин, не превышающих 255, выравнивание не требуется, в остальных случаях необходимо выравнивание.
value ::=BIT STRING(SIZE(0..7), …) ::= 0b0000001
-
Если длина превышает значения 64K или длина не определена, поле длины кодируется по правилам неопределённой длины.
value ::= BIT STRING (SIZE(0..6), …) ::= 0xAAAA
value ::= OCTET STRING (SIZE(0..1), …) ::= 0xAAAA
BIT STRING
OCTET STRING
Для «UNALIGNED» варианта:
-
Аналогично «ALIGNED», только без бит выравнивания.
СТРОКОВЫЕ ТИПЫ.
Строковые типы не так часто используются как остальные, и всё же не упомянуть про них было бы неправильно.
Строковые типы могут быть как с известным множителем, так и с неизвестным.
Строковые типы с известным множителем.
К таким типам относятся «IA5String», «PrintableString», «VisibleString (ISO646String)», «NumericString», «UniversalString» и «BMPString».
Будем рассматривать строковые типы, используя тип «IA5String». «IA5String» представляет из себя набор из 128 символов (первые 128 символов из набора ASCII алфавит). Это значит, что для кодирования каждого символа необходимо 7 бит.
В случае, когда количество символов ограничено, производится пересчёт количества бит на символ и битовых значений соответствующих каждому символу.
Например:
value ::= IA5String (FROM("AMEX"))
В данном варианте используются только 4 символа, следовательно для их кодировки необходимо использовать только 2 бита. Значения символов будут следующие, A=00, E=01, M=10, X=11.
В случае «UNALIGNED»:
-
Символьная строка занимает наименьшее количество бит.
value ::= IA5String (FROM("AMEX")^SIZE(3)) ::= “AXE”
-
Если длина строки фиксирована и не превышает 64К символов, добавление поля длины не требуется. В противном случае вначале добавляется поле длины.
-
При наличие поля длины, кодирование происходит аналогично типу «OCTET STRING» (помним что длина указывает число символов в сообщении).
value ::= IA5String::= “AXE”
В случае «ALIGNED»:
-
Символ всегда занимает 2i бит (2, 4, 8, 16 и т. д.).
-
Строки фиксированной длинны размером меньше или равные двум октетам не нуждаются в выравнивании на начало октета. То же самое касается строк с переменной длиной, максимальная длина которых меньше чем два октета.
value ::= IA5String (FROM("AMEX")^SIZE(3)) ::= “AXE”
-
Если длина строки фиксирована и не превышает 64К символов, добавление поля длины не требуется. В противном случае вначале добавляется поле длины.
-
Для неограниченных строк, или строк с длинами больше 64К-1, используется длина, закодированная согласно общему правилу кодирования длин.
value ::= IA5String::= “AXE”
-
При наличие расширения, добавляется бит расширения. В случае, когда бит расширения равен 1, оптимизация количества бит на символ не используется (т.е. каждый символ «IA5String» занимает 8 бит, «UniversalString», 32 бита), а длина кодируется согласно общему правилу кодирования длин и указывает количество символов.
Строковые типы с неизвестным множителем.
Кодируются аналогично «OCTET STRING», с длиной закодированной согласно общему правилу кодирования длин. Длина указывает количество октетов.
Сконструированные типы
SEQUENCE и SET type:
SEQUENCE и SET типы практически одинаковые. И первый и второй, представляют собой последовательности различных типов. Разница заключается лишь в том, что SEQUENCE представляет собой упорядоченную последовательность, а SET, неупорядоченную. Так как в типе SET последовательность не упорядочена, каждый тип входящий в неё должен использовать индивидуальный тэг. Тогда последовательность типов при кодировании (декодировании), будет определяться тэгами, согласно рекомендации ITU-T X.680 пункт 8.
Я буду рассматривать последовательности на примере SEQUENCE.
-
Если в SEQUENCE присутствуют опциональные поля, то в начале добавляется битовая стока длинной, равной количеству опциональных полей (один бит для каждого поля). Бит установленный в 1 указывает наличие опционального поля, 0 — отсутствие.
-
Опциональная битовая строка размером менее 64К (2 октета), всегда в значении «UNALIGNED». Опциональная битовая строка размером более 64К, дополнительно включает поле длины по правилам фрагментации неопределённой длины.
value ::= SEQUENCE{ first INTEGER (0..15) OPTIONAL, second INTEGER (0..15), third BOOLEAN OPTIONAL} ::= { second 10, third TRUE }
-
При наличии расширения, бит расширения ставится в 1. После основных полей ставится поле длины, указывающее размер дополнительной битовой последовательности. Дополнительная битовая последовательность указывает наличие (1) или отсутствие (0) каждого элемента в поле расширения.
-
Каждому элементу в поле расширения присутствует поле длины, закодированное в формате «неопределённой длины».
value ::= SEQUENCE{ first INTEGER (0..15) OPTIONAL, second INTEGER (0..15), third BOOLEAN OPTIONAL, …, fourth INTEGER (0..7), fifth BOOLEAN OPTIONAL} ::= { second 10, third TRUE, fourth 7, fifth TRUE }
В случае «UNALIGNED» формата кодируется аналогично, только без бит выравнивания.
SEQUENCE OF и SET OF type:
Данный тип представляет собой последовательность одного типа данных.
-
Если количество компонентов в SEQUENCE OF типе фиксировано, тогда компоненты следуют друг за другом безкаких-либо дополнительных полей.
-
Если количество компонентов не фиксировано, то вначале типа добавляется поле указывающее количество компонентов в типе.
-
Поле, указывающее количество компонентов в типе, кодируется аналогично полю длины для строковых типов.
value ::= SEQUENCE (SIZE(0..7)) OF INTEGER (0..15) ::= {10, 6, 9}
CHOICE type:
Тип CHOICE кодируется как индекс элемента, из списка.
-
При отсутствии бита расширения, индекс кодируется как ограниченное целочисленное число.
-
Все элементы входящие в тип индексируются начиная с 0. Порядок индексации должен соответствовать приоритетам из рекомендации ITU-T X.680 пункт 8.6. Элементы одного типа индексируются согласно своему расположению в списке.
Вот часть тегов из таблицы приоритетов из ITU-T X.680.
Как пример индекс Integer type должен быть меньше чем Octetstring type, даже если в списке он стоит после.
Навряд ли, вы встретите в рекомендациях, использующих ASN.1 PER, ситуации с CHOICE, включающим разные типы, раскиданные в разнобой, обычно списки построены так, что бы нумерация совпадала с расположением элемента (И самим лучше поступать так же, дабы не плодить хаос в космических масштабах).
Пример разных типов:
value ::= CHOICE{string OCTET STRING int1INTEGER(0...15) int2INTEGER(0...7)} ::= int2 = 5
Пример из NBAP протокола:
TransactionID ::= CHOICE { shortTransActionId INTEGER (0..127), longTransActionId INTEGER (0..32767) } ::= longTransActionId = 4767
-
Когда присутствует бит расширения равный 1, индекс кодируется согласно правилу кодирования небольших неотрицательных целых чисел (normally small non-negative whole number).
Если значение не превышает 63, первый бит (маркер) ставится равным 0, а следующие 6 бит кодируют значение.
Если значение превышает 63, первый бит ставится равным 1, после значение кодируется как целое число неограниченной длины.
-
При наличии расширения (extension bit = 1), поля в блоке расширения переиндексируются начиная с 0. Поля из блока расширения, при добавлении, включают в себя помимо поля значения, так же поле длины.
value ::= CHOICE { string OCTET STRING, int1 INTEGER (0..7) , value2INTEGER (0..15), …, flag BOOLEAN, ~~~ <64-ый элемент> int3 INTEGER (0..15) } ::= int3 = 7
На этом с описанием типов можно закончить. Ещё раз напомню, здесь я описал наиболее часто используемые типы, про всё остальное можно почитать в рекомендации «ITU-T REC. X.691».
Применение на практике
Надеюсь, всё выше сказанное, не представляет особой сложности. И всё же, предлагаю собрать всё вместе и использовать полученные знания на практике. В качестве практического примера мы попробуем декодировать одну из команд NBAP протокола (используемого для передачи сигнализации между RNC и NodeB, через Iub интерфейс). Файл с примером, можно скачать с wireshark wiki (https://wiki.wireshark.org/NBAP). В качестве тестовой команды, возьмём ответ на установление радиоканала «RadioLinkSetupResponse».
Ну, что ж, обновим свой чай и приступим.
Итак, на вход нам приходит следующая последовательность: {0x20, 0x1b, 0x22, 0x01, 0x6f, 0x3f, 0x00, 0x00, 0x04, 0x00, 0x2c, 0x40, 0x02, 0x00, 0x61, 0x00, 0x8f, 0x40, 0x02, 0x00, 0x00, 0x00, 0x28, 0x40, 0x02, 0x00, 0x01, 0x00, 0xe0, 0x40, 0x26, 0x00, 0x00, 0xdc, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}.
Первым делом, открываем рекомендацию на NBAP протокол (3GPP TS 25.433) и смотрим описание команд.
NBAP-PDU представляет собой тип CHOICE:
NBAP-PDU ::= CHOICE { initiatingMessage InitiatingMessage, succesfulOutcome SuccessfulOutcome, unsuccesfulOutcome UnsuccessfulOutcome, outcome Outcome, ... }
Здесь присутствуют 4 основных значения (индексация 0-3) и бит расширения. То есть всего тип занимает 3 бита.
0x20 = 0b0010 0000.
0 – extention bit
01 – value = succesfulOutcome
NBAP-PDU в данном случае принимает значение «succesfulOutcome».
SuccesfulOutcome представляет из себя тип SEQUENCE:
SuccessfulOutcome ::= SEQUENCE { procedureID NBAP-ELEMENTARY-PROCEDURE.&procedureID ({NBAP-ELEMENTARY-PROCEDURES}), criticality NBAP-ELEMENTARY-PROCEDURE.&criticality ({NBAP-ELEMENTARY-PROCEDURES}{@procedureID}), messageDiscriminator NBAP-ELEMENTARY-PROCEDURE.&messageDiscriminator({NBAP-ELEMENTARY-PROCEDURES}{@procedureID}), transactionID TransactionID, value NBAP-ELEMENTARY-PROCEDURE.&SuccessfulOutcome({NBAP-ELEMENTARY-PROCEDURES}{@procedureID}) }
Опциональных полей здесь нет, поэтому дополнительное битовое поле отсутствует.
Первым идёт поле «procedureID»:
ProcedureID ::= SEQUENCE { procedureCode ProcedureCode, ddMode ENUMERATED { tdd, fdd, common, ... } } ProcedureCode ::= INTEGER (0..255)
ProcedureID так же не имеет опциональных полей. Первое поле представляет тип INTEGER и занимает 1 октет. Второе поле имеет тип ENUMERATED, может принимать одно из трёх значений (0-2) и имеет также возможность расширения (для кодирования необходимо 3 бита).
Так как NBAP кодируется «ALIGNED» представлением, а тип INTEGER занимает один октет, поле «ProcedureCode», должно быть выравнено на начало байта. Следующие 2 октета = {0x1b, 0x22}.
ProcedureCode:
value = 0x1b = 27
0x22 = 0b0010 0010
ddMode:
0 – extention bit
01 – value = fdd
Следующее поле «criticality»:
Criticality ::= ENUMERATED { reject, ignore, notify }
Criticality может принимать 3 значения (0..2) и занимает 2 бита. Помним, что кодируется как INTEGER ограниченной длины (при размере менее одного октета выравнивание не требуется). Значение расположено сразу за полем «ddMode».
0x22 = 0b0010 0010
criticality = 00 = reject
Следующее поле «messageDiscriminator»:
MessageDiscriminator ::= ENUMERATED { common, dedicated }
Принимает 2 значения и занимает 1 бит. Расположено сразу за полем «Criticality».
0x22 = 0b0010 0010
MessageDiscriminator = 0 = common
Следующее поле «transactionID»:
TransactionID ::= CHOICE { shortTransActionId INTEGER (0..127), longTransActionId INTEGER (0..32767) }
Тип CHOICE из двух значений с индексами 0 и 1, занимает 1 бит, далее идёт одно из двух значений «shortTransActionId» длинной один октет, или «longTransActionId», длиной 2 октета. Поля должны быть выравнены на начало байта (для INTEGER типа, длина 1 октет и выше).
0x22 = 0b0010 0010
CHOICE = 1 = longTransActionId
value = {0x01, 0x6f} = 367
Последнее поле «value»:
По сути представляет собой контейнер, декодирующийся исходя из значения «procedureID». Контейнер имеет поле длины (неограниченное целое число) и поле с контентом.
Длина = 0x3f = 0b0011 1111 = 63
Для наглядности, распишем все по ля по октетами и представим это в виде таблицы.
Итак, мы разобрали заголовок. Пора переходить к контенту. Контент представляет следующую последовательность: {0x00, 0x00, 0x04, 0x00, 0x2c, 0x40, 0x02, 0x00, 0x61, 0x00, 0x8f, 0x40, 0x02, 0x00, 0x00, 0x00, 0x28, 0x40, 0x02, 0x00, 0x01, 0x00, 0xe0, 0x40, 0x26, 0x00, 0x00, 0xdc, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}.
Для начала разберёмся с типом команды. В нашем случае «ProcedureCode» равен 27. Смотрим в разделе констант значения «ProcedureCode».
Id-radioLinkSetup ProcedureCode ::= 27
Наш «ProcedureCode» соответствует команде «RadioLinkSetup». DDMode стоит в значении «FDD». Смотрим как выглядит наша процедура.
-- *** RadioLinkSetup (FDD) *** radioLinkSetupFDD NBAP-ELEMENTARY-PROCEDURE ::= { INITIATING MESSAGE RadioLinkSetupRequestFDD SUCCESSFUL OUTCOME RadioLinkSetupResponseFDD UNSUCCESSFUL OUTCOME RadioLinkSetupFailureFDD MESSAGE DISCRIMINATOR common PROCEDURE ID { procedureCode id-radioLinkSetup, ddMode fdd } CRITICALITY reject }
Тип PDU, в нашем примере имеет значение «SuccessfulOutcome», а это значит, что мы рассматриваем команду «RadioLinkSetupResponseFDD».
RadioLinkSetupResponseFDD ::= SEQUENCE { protocolIEs ProtocolIE-Container {{RadioLinkSetupResponseFDD-IEs}}, protocolExtensions ProtocolExtensionContainer {{RadioLinkSetupResponseFDD-Extensions}} OPTIONAL, ... }
Команда «RadioLinkSetupResponseFDD», представляет тип «SEQUENCE», состоящий из двух полей, одно из которых опциональное, и с полем расширения. Это значит, что начинаться тип будет с двух однобитных полей (бит extention и бит отвечающий за опциональное поле). В нашем случае первый октет равен 0. Extention = 0 и optional = 0, то есть наша команда состоит только из одного поля «protocolIEs».
ProtocolIE-Container {NBAP-PROTOCOL-IES:IesSetParam}::= SEQUENCE SIZE (0..maxProtocolIEs)) OF ProtocolIE-Field {{IesSetParam}} ProtocolIE-Field {NBAP-PROTOCOL-IES : IEsSetParam} ::= SEQUENCE { id NBAP-PROTOCOL-IES.&id ({IEsSetParam}), criticality NBAP-PROTOCOL-IES.&criticality ({IEsSetParam}{@id}), value NBAP-PROTOCOL-IES.&Value ({IEsSetParam}{@id}) }
«ProtocolIE-Container», представляет из себя последовательность классов, соответствующих типу «RadioLinkSetupResponseFDD-Ies». Так как тип представляет собой последовательность SEQUENCE OF, вначале будет добавлено битовое поле, указывающее количество элементов в последовательности.
maxProtocolIEs INTEGER ::= 65535
Размер битового поля составляет 2 октета, что необходимо для того, что бы закодировать 65535 значений. Так как длина составляет 2 октета, поле начинается с начала октета.
Итак, наша команда состоит из четырёх элементов закодированных последовательностью: {0x00, 0x2c, 0x40, 0x02, 0x00, 0x61, 0x00, 0x8f, 0x40, 0x02, 0x00, 0x00, 0x00, 0x28, 0x40, 0x02, 0x00, 0x01, 0x00, 0xe0, 0x40, 0x26, 0x00, 0x00, 0xdc, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}.
Каждый элемент является типом «SEQUENCE», состоит из трёх обязательных полей, соответствующих классу «NBAP-PROTOCOL-IES» и должен принимать значения, соответствующие «RadioLinkSetupResponseFDD-Ies».
NBAP-PROTOCOL-IES ::= CLASS { &id ProtocolIE-ID UNIQUE, &criticality Criticality, &Value, &presence Presence }
RadioLinkSetupResponseFDD-IEs NBAP-PROTOCOL-IES ::= { { ID id-CRNC-CommunicationContextID CRITICALITY ignore TYPE CRNC-CommunicationContextID PRESENCE mandatory }| { ID id-NodeB-CommunicationContextID CRITICALITY ignore TYPE NodeB-CommunicationContextID PRESENCE mandatory }| { ID id-CommunicationControlPortID CRITICALITY ignore TYPE CommunicationControlPortID PRESENCE mandatory }| { ID id-RL-InformationResponseList-RL-SetupRspFDD CRITICALITY ignore TYPE RL-InformationResponseList-RL-SetupRspFDD PRESENCE mandatory }| { ID id-CriticalityDiagnostics CRITICALITY ignore TYPE CriticalityDiagnostics PRESENCE optional }, ... }
Первое поле (id), является типом «INTEGER» и занимает 2 байта.
ProtocolIE-ID ::= INTEGER (0..maxProtocolIEs) maxProtocolIEs INTEGER ::= 65535
Второе поле (criticality), является типом «ENUMERATED» и занимает 2 бита.
Criticality ::= ENUMERATED { reject, ignore, notify }
Третье поле, представляет из себя целочисленное значение неограниченной длины. Включает в себя поле длины и поле с контентом, декодирующимся согласно значению поля «id».
Обратите внимание, в данной команде присутствуют 4 обязательных элемента, следующих типов «CRNC-CommunicationContextID», «NodeB-CommunicationContextID», «CommunicationControlPortID» и «RL-InformationResponseList-RL-SetupRspFDD». Так же есть один опциональный элемент, типа «CriticalityDiagnostics» и возможность добавления других элементов. Во всех описанных элементах, параметр «Criticality» должен принимать значение «ignore».
Первый элемент:
{0x00, 0x2c, 0x40, 0x02, 0x00, 0x61}
ID = 44 (id-CRNC-CommunicationContextID)
id-CRNC-CommunicationContextID ProtocolIE-ID ::= 44
Criticality = ignore
Value = {0x00, 0x61}, должно декодироваться в соответствии с типом «CRNC-CommunicationContextID».
«CRNC-CommunicationContextID» кодируется типом INTEGER и занимает следующие 2 октета.
CRNC-CommunicationContextID ::= INTEGER (0..1048575)
Диапазон значений превышает 64К, а значит в начале будет добавлено поле длины, размером 2 бита, принимающее одно из 3х значений (0 — длина 1 октет, 1 — 2 октета, 2 — 3 октета). Далее следуют биты выравнивания и само значение, выравненное на начало октета.
В нашем случае длина = 0b00, что соответствует длине в один октет. То есть суммарно тип занимает 2 октета, что соответствует длине, указанной для «контейнера».
CRNC-CommunicationContextID = 97
Второй элемент:
{0x00, 0x8f, 0x40, 0x02, 0x00, 0x00}
ID = 143 (id-NodeB-CommunicationContextID)
id-NodeB-CommunicationContextID ProtocolIE-ID ::= 143
Criticality = ignore
Value = {0x00, 0x00}, должно декодироваться в соответствии с типом «NodeB-CommunicationContextID».
«NodeB-CommunicationContextID» кодируется типом INTEGER и занимает следующие 2 октета.
NodeB-CommunicationContextID ::= INTEGER (0..1048575)
NodeB-CommunicationContextID = 0
Третий элемент:
{0x00, 0x28, 0x40, 0x02, 0x00, 0x01}
ID = 40 (id-CommunicationControlPortID)
id-CommunicationControlPortID ProtocolIE-ID ::= 40
Criticality = ignore
Value = {0x00, 0x01}, должно декодироваться в соответствии с типом «CommunicationControlPortID».
«CommunicationControlPortID» кодируется типом INTEGER и занимает два октета.
CommunicationControlPortID ::= INTEGER (0..65535)
Диапазон значений равен 64К, а значит тип занимает 2 октета, без добавления длины.
CommunicationControlPortID = 1
Четвёртый элемент:
{0x00, 0xe0, 0x40, 0x26, 0x00, 0x00, 0xdc, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}
ID = 224 (id-RL-InformationResponseList-RL-SetupRspFDD)
id-RL-InformationResponseList-RL-SetupRspFDD ProtocolIE-ID ::= 224
Criticality = ignore
Value = {0x00, 0x00, 0xdc, 0x40, 0x21, 0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, должно декодироваться в соответствии с типом «RL-InformationResponseList-RL-SetupRspFDD».
«RL-InformationResponseList-RL-SetupRspFDD», представляет из себя тип «SEQUENCE OF»
RL-InformationResponseList-RL-SetupRspFDD ::= SEQUENCE (SIZE (1..maxNrOfRLs)) OF ProtocolIE-Single-Container{{ RL-InformationResponseItemIE-RLSetupRspFDD }} maxNrOfRLs INTEGER ::= 16
Разница между минимальным и максимальным значением количества элементов в последовательности равна 15. Значит для кодирования количества элементов нам потребуется 4 бита.
Каждый элемент является типом «ProtocolIE-Single-Container», в соответствии с «RL-InformationResponseItemIE-RLSetupRspFDD».
ProtocolIE-Single-Container {NBAP-PROTOCOL-IES : IEsSetParam} ::= ProtocolIE-Field {{IesSetParam}} ProtocolIE-Field {NBAP-PROTOCOL-IES : IEsSetParam} ::= SEQUENCE { id NBAP-PROTOCOL-IES.&id ({IEsSetParam}), criticality NBAP-PROTOCOL-IES.&criticality ({IEsSetParam}{@id}), value NBAP-PROTOCOL-IES.&Value ({IEsSetParam}{@id}) }
Вспоминаем класс «NBAP-PROTOCOL-IES», описанный выше, и получаем следующую картину.
Помним, что поле с размером кодирует разность между максимальным и минимальным значениями. В нашем случае минимальное количество элементов последовательности равно 1, а закодированное значение равно 0. То есть имеем последовательность из одного элемента.
Элемент должен иметь следующие поля.
RL-InformationResponseItemIE-RL-SetupRspFDD NBAP-PROTOCOL-IES ::= {{ ID id-RL-InformationResponseItem-RL-SetupRspFDD CRITICALITY ignore TYPE RL-InformationResponseItem-RL-SetupRspFDD PRESENCE mandatory }}
ID = 220 (id-RL-InformationResponseItem-RL-SetupRspFDD)
id-RL-InformationResponseItem-RL-SetupRspFDD ProtocolIE-ID ::= 220
Criticality = ignore
Value = {0x00, 0x00, 0x00, 0x00, 0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}, должно декодироваться в соответствии с типом «RL-InformationResponseItem-RL-SetupRspFDD».
Как видим, значения полей, соответствуют типу описанному в рекомендации.
Далее переходим к декодированию «RL-InformationResponseItem-RL-SetupRspFDD».
RL-InformationResponseItem-RL-SetupRspFDD ::= SEQUENCE { rL-ID RL-ID, rL-Set-ID RL-Set-ID, received-total-wide-band-power Received-total-wide-band-power-Value, diversityIndication DiversityIndication-RL-SetupRspFDD, not-Used-dSCH-InformationResponseList NULL OPTIONAL, sSDT-SupportIndicator SSDT-SupportIndicator, iE-Extensions ProtocolExtensionContainer { { RL-InformationResponseItem-RL-SetupRspFDD-ExtIEs} } OPTIONAL, ... }
Итак, имеем тип «SEQUENCE», с возможностью расширения и двумя опциональными полями, «not-Used-dSCH-InformationResponseList» и «iE-Extensions». Значит первые три бита представляют собой бит расширения и два бита опциональных полей. Следом идут типы «RL-ID» и «RL-Set-ID».
RL-ID ::= INTEGER (0..31)
RL-Set-ID ::= INTEGER (0..31)
Оба типа целочисленные, размером по пять бит, не нуждающиеся в выравнивание (так как каждый занимает менее одного октета).
Следом идёт поле, с типом «Received-total-wide-band-power-Value».
Received-total-wide-band-power-Value ::= INTEGER(0..621)
Тип целочисленный, занимает два октета и требует выравнивания для «ALIGNED» формата.
Давайте остановимся, и распишем вышеописанные типы в виде таблицы, так как следующий тип будет довольно громоздкий, и лучше вынести его отдельно.
Поле расширения и опциональные поля отсутствуют. Поля «rl-ID», «rl-Set-ID» и «received-total-wide-band-power» принимают нулевые значения.
Ну всё, теперь первые четыре октета можно убрать, обновить чай и перейти к типу «DiversityIndication-RL-SetupRspFDD».
На всякий случай продублирую значение «Value» без первых четырёх октетов: {0x80, 0x18, 0x1f, 0x20, 0x1f, 0x68, 0x4f, 0x80, 0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}.
DiversityIndication-RL-SetupRspFDD ::= CHOICE { combining Combining-RL-SetupRspFDD, nonCombiningOrFirstRL NonCombiningOrFirstRL-RL-SetupRspFDD }
Тип «CHOICE», состоящий из двух полей занимает один бит.
0x80 = 0b1000 0000
В нашем случае, первый бит равен 1, а значит «DiversityIndication-RL-SetupRspFDD» принимает значение «nonCombiningOrFirstRL».
«nonCombiningOrFirstRL», является типом «NonCombiningOrFirstRL-RL-SetupRspFDD».
NonCombiningOrFirstRL-RL-SetupRspFDD ::= SEQUENCE { dCH-InformationResponse DCH-InformationResponse, iE-Extensions ProtocolExtensionContainer { { NonCombiningOrFirstRLItem-RL-SetupRspFDD-ExtIEs} } OPTIONAL, ... }
Тип «SEQUNCE» с возможностью расширения и одним опциональным полем.
0x80 = 0b1000 0000 — расширение отсутствует
0x80 = 0b1000 0000 — опциональное поле отсутствует
Начиная с 4 бита, начинается тип «DCH-InformationResponse»
DCH-InformationResponse ::= SEQUENCE (SIZE (1..maxNrOfDCHs)) OF DCH-InformationResponseItem maxNrOfDCHs INTEGER ::= 128
Максимальное значение для кодирования количества элементов равно 127 (128-1). Для его кодирования требуется 7 бит (помним, что при длинах менее одного октета, выравнивание не требуется).
В нашем примере, количество элементов последовательности равно 1. имеем один элемент типа «DCH-InformationResponseItem».
DCH-InformationResponseItem ::= SEQUENCE { dCH-ID DCH-ID, bindingID BindingID OPTIONAL, transportLayerAddress TransportLayerAddress OPTIONAL, iE-Extensions ProtocolExtensionContainer { { DCH-InformationResponseItem-ExtIEs} } OPTIONAL, ... }
Элемент, представляет собой тип «SEQUENCE», с одним полем расширения и тремя опциональными полями.
0x18 = 0b0001 1000 — расширение отсутствует
0x18 = 0b0001 1000 — присутствует поле «bindingID»
0x18 = 0b0001 1000 — присутствует поле «transportLayerAddress»
0x18 = 0b0001 1000 — поле «iE-Extensions», отсутствует
DCH-ID ::= INTEGER (0..255)
Тип «DCH-ID» — целочисленный и занимает 1 октет.
BindingID ::= OCTET STRING (SIZE (1..4, …))
Тип «BindingID», имеет возможность расширения. Кодируется с добавлением поля длины. Поле длинны кодируется двумя битами (максимальное значение 3), когда бит расширения равен 0, либо как неограниченное целочисленное значения в противоположном случае.
TransportLayerAddress ::= BIT STRING (SIZE (1..160, …))
Тип «TransportLayerAddress», кодируется аналогично типу «BindingID», за исключением того, что под длину, при нулевом бите расширения, выделяется один октет (максимальное значение 159).
DCH-ID:
Принимает значение 31.
BindingID:
Расширение отсутствует, длина 2 октета (так как минимальное значение равно 1). Так как размер равен 2 октета, требуется выравнивание, для «ALIGNED» формата. Принимает значение 8040.
TransportLayerAddress:
Расширение отсутствует, длина 160 бит (так как минимальное значение равно 1). Так как значение поля длины не превышает 255, выравнивания для поля длины не требуется. В то время, как для данных ( размер больше 16 бит) требуется выравнивание, для «ALIGNED» формата. Принимает значение {0x35, 0x00, 0x01, 0x0a, 0x81, 0x83, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}.
Ну что ж, с полем «diversityIndication» мы закончили.
У нас остался один октет (0x80) и последнее поле «sSDT-SupportIndicator».
Поле имеет тип «SSDT-SupportIndicator», который в свою очередь является типом «ENUMERATED», состоящим из двух значений:
SSDT-SupportIndicator ::= ENUMERATED { not-Used-sSDT-Supported, sSDT-not-supported }
«SSDT-SupportIndicator» принимает значение «sSDT-not-supported».
Ура! Все поля заполнены. Сообщение декодировано.
Подводя итог
Метод кодирования «PER», не просто так занял свою нишу в сетях сотовой связи. Используемые методы позволяют если и не максимально, то очень сильно сократить закодированные последовательности, при этом не ограничивая разработчика в размерах исходных данных.
Понимание методов кодирования позволит вам проектировать структуры, дающие наиболее оптимальный размер закодированных данных (особенно, если вы используете «ALIGNED» формат).
ссылка на оригинал статьи https://habr.com/ru/articles/853738/
Добавить комментарий