Журнал LinuxFormat - перейти на главную

LXF157:Язы­ки про­грам­ми­ро­вания. Erlang

Материал из Linuxformat
Перейти к: навигация, поиск


Erlang Опи­сы­ва­ет­ся сле­дую­щей фор­му­лой: функ­цио­наль­ный язык + про­цес­сы

Erlang: Ма­гия би­то­вых строк 3

Ан­д­рей Уша­ков за­вер­ша­ет «боль­шое кол­дов­ст­во» с би­то­вы­ми стро­ка­ми, рас­ко­ди­руя их.

Наш эксперт

Ан­д­рей Уша­ков ак­тив­но при­бли­жа­ет тот день, ко­гда функ­цио­наль­ные язы­ки ста­нут мейн­ст­ри­мом.

Bэтом но­ме­ре мы про­дол­жим об­су­ж­дение боль­шо­го при­ме­ра ис­поль­зо­вания би­то­вых строк: рас­смот­рим, ка­ким об­ра­зом объ­ек­ты Erlang, со­гласно пра­ви­ла­м ASN.1 BER (то, чем мы занима­лись в про­шлом но­ме­ре), рас­ко­ди­ро­вать об­рат­но.

Пер­вый ша­г, как и во всех дру­гих при­ме­рах – оп­ре­де­ление мо­ду­ля с функ­цио­наль­ностью при­ме­ра, под­клю­чение фай­лов с оп­ре­де­ления­ми и оп­ре­де­ление спи­ска экс­пор­ти­руе­мых функ­ций. Экс­пор­ти­ру­ем мы все­го две функ­ции: build/1 для по­строения функ­ции дис­пет­че­ра, ко­то­рая вы­би­рает должную функ­цию для де­ко­ди­ро­вания дан­ных по их ти­пу, и decode/2, для самого де­ко­ди­ро­вания.

-module(asn1_decoder).

-include(“asn1_tag.hrl”).

-export([build/1, decode/2]).

Функ­ция-дис­пет­чер ис­поль­зу­ет спи­сок пар (кор­те­жей из двух зна­чений) из ти­па дан­ных и со­от­вет­ст­вую­щей ему функ­ции для де­ко­ди­ро­вания объ­ек­та это­го ти­па. При соз­дании функ­ции-дис­пет­че­ра мы ис­поль­зу­ем как внут­ренний (за­дан­ный на­ми) спи­сок пар из ти­па дан­ных и со­от­вет­ст­вую­щей ему функ­ции для де­ко­ди­ро­вания, так и внешний (за­да­вае­мый поль­зо­ва­те­лем функ­ции че­рез един­ст­вен­ный па­ра­метр) спи­сок та­ких пар.

Внут­ренний спи­сок пар об­ра­ба­ты­ва­ет об­щие си­туа­ции де­ко­ди­ро­вания (когда объ­ект яв­ля­ет­ся спи­ском, кор­те­жем, це­лым чис­лом и т. д.); внешний спи­сок (за­да­вае­мый поль­зо­ва­те­лем) слу­жит для об­ра­бот­ки спе­ци­фич­ных си­туа­ций де­ко­ди­ро­вания – на­при­мер, ес­ли мы хо­тим де­ко­ди­ро­вать за­пи­си от­лич­ным от обыч­ных кор­те­жей об­ра­зом. Для это­го па­ры из внешнего спи­ска идут всегда пе­ред па­ра­ми из внут­реннего спи­ска, что оз­на­ча­ет бо­лее вы­со­кий их при­ори­тет. Бо­лее то­го, ес­ли од­на па­ра идет пе­ред дру­гой, то это оз­на­ча­ет, что при­ори­тет у этой па­ры вы­ше, т. к. эта па­ра бу­дет ис­поль­зо­ва­на в про­це­ду­ре вы­бо­ра под­хо­дя­щей функ­ции де­ко­ди­ро­вания пер­вой.

Вполне оче­вид­но, что внешний спи­сок пар, ис­поль­зуе­мый для по­строения функ­ции-дис­пет­че­ра для де­ко­ди­ро­вания объ­ек­тов по их ти­пу, дол­жен со­от­вет­ст­во­вать спи­ску пар функ­ций, ис­поль­зуе­мый для по­строения со­от­вет­ст­вую­щей функ­ции дис­пет­че­ра для ко­ди­ро­вания объ­ек­тов (об этом мы го­во­ри­ли в пре­ды­ду­щем но­ме­ре). Со­от­вет­ст­вие этих спи­сков оз­на­ча­ет сле­дую­щее: функ­ции, ко­ди­рую­щие и де­ко­ди­рую­щие одни и те же ти­пы дан­ных долж­ны рас­по­ла­гать­ся оди­на­ко­во в со­от­вет­ст­вую­щих спи­сках. Ес­ли не со­блю­дать со­от­вет­ст­вие спи­сков, то воз­мож­ны си­туа­ции, когда невоз­мож­но де­ко­ди­ро­вать за­ко­ди­ро­ван­ные объ­ек­ты, ли­бо когда мы ко­ди­ру­ем объ­ект од­но­го ти­па, а при де­ко­ди­ро­вании по­лу­ча­ем объ­ект дру­го­го ти­па, от­лич­но­го от ис­ход­но­го.

build(ExternalDecoders) when is_list(ExternalDecoders) ->

InternalDecoders =

[

{#tag{class = universal, form = primitive, tag_value = 1}, fun decode_boolean/2},

{#tag{class = universal, form = primitive, tag_value = 2}, fun decode_integer/2},

{#tag{class = universal, form = primitive, tag_value = 9}, fun decode_real/2},

{#tag{class = universal, form = primitive, tag_value = 4}, fun decode_octetstring/2},

{#tag{class = universal, form = primitive, tag_value = 3}, fun decode_bitstring/2},

{#tag{class = universal, form = constructed, tag_value = 16}, fun decode_sequence/2},

{#tag{class = universal, form = constructed, tag_value = 32}, fun decode_tuple/2},

{#tag{class = universal, form = primitive, tag_value = 33}, fun decode_atom/2}

],

TotalDecodersList = ExternalDecoders ++ InternalDecoders,

fun(Binary, DecodeDispatcher) ->

{Tag, _BinaryRest} = decode_tag(Binary),

case lists:keyfind(Tag, 1, TotalDecodersList) of

{Tag, Decoder} -> {ok, Decoder(Binary, DecodeDispatcher)};

false -> false

end

end.

Функ­ция decode/2 ис­поль­зу­ет функ­цию-дис­пет­чер де­ко­ди­ро­вания, ко­то­рую мы по­строи­ли при по­мо­щи функ­ции build/1; эту функ­цию-дис­пет­чер мы пе­ре­да­ем в ка­че­­ст­ве пер­во­го па­ра­мет­ра (в ка­че­­ст­ве вто­ро­го па­ра­мет­ра пе­ре­да­ет­ся би­то­вая стро­ка, со­дер­жа­щая за­ко­ди­ро­ван­ный объ­ект). Ра­бо­та функ­ции decode/2 осно­ва­на на ис­поль­зо­вании функ­ции-дис­пет­че­ра де­ко­ди­ро­вания: мы вы­зы­ва­ем функ­цию-дис­пет­чер, пе­ре­да­вая в ка­че­­ст­ве па­ра­мет­ров де­ко­ди­руе­мый объ­ект и са­му функ­цию-дис­пет­чер де­ко­ди­ро­вания. Ес­ли пе­ре­да­вае­мые дан­ные мо­гут быть де­ко­ди­ро­ва­ны в объ­ект, то бу­дет воз­вра­щен кор­теж, со­стоя­щий из ато­ма ok и де­ко­ди­ро­ван­но­го объ­ек­та; ес­ли же пе­ре­да­вае­мые дан­ные не мо­гут быть де­ко­ди­ро­ва­ны, то бу­дет воз­вра­щен атом false. В по­следнем слу­чае, мы генери­ру­ем ис­клю­чение вре­мени вы­полнения.

Сле­ду­ет так­же ска­зать, что в от­ли­чие от функ­ции ко­ди­ро­вания объ­ек­тов encode/2 (ко­то­рую мы при­во­ди­ли в пре­ды­ду­щем но­ме­ре), для ра­бо­ты функ­ции decode/2 не нуж­на функ­ция для по­ис­ка пер­вой под­хо­дя­щей па­ры, на­по­до­бие функ­ции first/3 (опять же при­ве­ден­ной в пре­ды­ду­щем но­ме­ре). Это свя­за­но с тем, что в функ­ции decode/2 по­иск па­ры про­ис­хо­дит по клю­чу (ко­то­рым яв­ля­ет­ся тип дан­ных), и для этой опе­ра­ции доста­точ­но функ­ции lists:keyfind/3. В от­ли­чие от функ­ции decode/2, в функ­ции encode/2 по­иск пер­вой под­хо­дя­щей па­ры про­из­во­дил­ся в спи­ске пар функ­ций, и кри­те­ри­ем окон­чания по­ис­ка бы­ло на­хо­ж­дение па­ры, пер­вая функ­ция ко­то­рой воз­вра­ща­ла атом true для ис­ход­но­го объ­ек­та. Та­кая функ­цио­наль­ность не реа­ли­зо­ва­на ни сре­ди функ­ций мо­ду­ля lists (мо­ду­ля ра­бо­ты со спи­ска­ми), ни сре­ди функ­ций лю­бых дру­гих стан­дарт­ных мо­ду­лей.

decode(Value, DecodeDispatcher) ->

case DecodeDispatcher(Value, DecodeDispatcher) of

{ok, Result} -> Result;

false -> erlang:error(unsuitable_value)

end.

Стан­дар­ты ASN.1

» ITU-T Rec. X.680 I ISO/IEC 8824-1. Спе­ци­фи­ка­ция на ба­зо­вую но­та­цию.

» ITU-T Rec. X.681 I ISO/IEC 8824-2. Спе­ци­фи­ка­ция на ин­фор­ма­ци­он­ные объ­ек­ты.

» ITU-T Rec. X.682 I ISO/IEC 8824-3. Спе­ци­фи­ка­ция на ог­ра­ни­че­ния.

» ITU-T Rec. X.683 I ISO/IEC 8824-4. Спе­ци­фи­ка­ция на па­ра­мет­ри­за­цию ASN.1.

» ITU-T Rec. X.690 I ISO/IEC 8825-1. Спе­ци­фи­ка­ция на BER (Basic encoding rules), CER (Canonical encoding rules) и DER (Distinguished encoding rules).

» ITU-T Rec. X.691 I ISO/IEC 8825-2. Спе­ци­фи­ка­ция на PER (Packed encoding rules).

» ITU-T Rec. X.692 I ISO/IEC 8825-3. Спе­ци­фи­ка­ция на ECN (Encoding control notation).

» ITU-T Rec. X.693 I ISO/IEC 8825-4. Спе­ци­фи­ка­ция на XER (XML Encoding rules).

» ITU-T Rec. X.694 I ISO/IEC 8825-5. Спе­ци­фи­ка­ция на ото­бра­же­ние на XSD.

» ITU-T Rec. X.695 I ISO/IEC 8825-6. Спе­ци­фи­ка­ция на ре­ги­ст­ра­цию и при­ме­не­ние ин­ст­рук­ций ко­ди­ро­ва­ния PER (Packed encoding rules).

Те­перь пе­рей­дем непо­сред­ст­вен­но к де­ко­ди­ро­ванию дан­ных. В пер­вую оче­редь нам необ­хо­ди­мо уметь де­ко­ди­ро­вать тип дан­ных (он же тэг дан­ных). Свя­за­но это с тем, что по ти­пу дан­ных мы вы­би­ра­ем по­том под­хо­дя­щую функ­цию для де­ко­ди­ро­вания са­мо­го объ­ек­та. Как уже го­во­ри­лось, тип дан­ных со­сто­ит из трех ком­понент: клас­са, фор­мы и иден­ти­фи­ка­то­ра ти­па дан­ных. Класс и фор­ма име­ют фик­си­ро­ван­ный раз­мер – 2 и 1 бит со­от­вет­ст­вен­но. Спо­соб ко­ди­ро­вания иден­ти­фи­ка­то­ра ти­па дан­ных за­ви­сит от его зна­чения. Ес­ли зна­чение иден­ти­фи­ка­то­ра мень­ше 31, то он занима­ет остав­шие­ся 5 бит ок­те­та (бай­та) пол­но­стью. Ес­ли же его зна­чение боль­ше или рав­но 31, то в остав­шие­ся 5 бит ок­те­та за­пи­сы­ва­ет­ся зна­чение 2#11111, по­сле че­го идет зна­чение иден­ти­фи­ка­то­ра, за­ко­ди­ро­ван­ное бо­лее слож­ным спо­со­бом, о ко­то­ром мы по­го­во­рим ниже. За де­ко­ди­ро­вание ти­па дан­ных от­ве­ча­ет ме­тод decode_tag/1. Вхо­дя­щий па­ра­метр у него один – би­то­вая стро­ка с дан­ны­ми для де­ко­ди­ро­вания. Вхо­дя­щий па­ра­метр в за­го­лов­ке ме­то­да при по­мо­щи опе­ра­ции со­от­вет­ст­вия шаб­ло­ну [pattern matching] раз­би­ва­ет­ся на 2 би­та для клас­са, 1 бит для фор­мы, 5 би­то­вый сег­мент (ли­бо для иден­ти­фи­ка­то­ра ти­па, ли­бо для ве­ли­чи­ны 2#11111) и остав­шую­ся часть би­то­вой стро­ки. Зна­чение 5 би­то­во­го сег­мен­та оп­ре­де­ля­ет, мож­но ли сра­зу де­ко­ди­ро­вать иден­ти­фи­ка­тор ти­па дан­ных или же необ­хо­ди­мо из­влечь из остав­шей­ся би­то­вой стро­ки еще дан­ные. По­это­му вполне ло­гич­но, что на осно­ве это­го зна­чения мы оп­ре­де­ля­ем два ва­ри­ан­та ме­то­да decode_tag/1.

decode_tag(<<Class:2, Form:1, 2#11111:5, Rest/binary>>) ->

{TagValue, TagRest} = decode_tag_value(Rest, []),

{#tag{class = decode_tag_class(Class), form = decode_tag_form(Form), tag_value = TagValue}, TagRest};

decode_tag(<<Class:2, Form:1, TagValue:5, Rest/binary>>) ->

{#tag{class = decode_tag_class(Class), form = decode_tag_form(Form), tag_value = TagValue}, Rest}.

Ме­тод decode_tag_class/1 слу­жит для де­ко­ди­ро­вания зна­чения клас­са ти­па дан­ных в со­от­вет­ст­вую­щий пре­до­пре­де­лен­ный атом. Для де­ко­ди­ро­вания мы ис­поль­зу­ем несколь­ко ва­ри­ан­тов функ­ции decode_tag_class/1, ко­то­рые по­кры­ва­ют весь диа­па­зон воз­мож­ных зна­чений для клас­са ти­па дан­ных.

decode_tag_class(2#00) -> universal;

decode_tag_class(2#01) -> application;

decode_tag_class(2#10) -> context_specific;

decode_tag_class(2#11) -> private.

Ме­тод decode_tag_form/1 слу­жит для де­ко­ди­ро­вания зна­чения фор­мы ти­па дан­ных; прин­ци­пы его ра­бо­ты пол­но­стью ана­ло­гич­ны пре­ды­ду­ще­му ме­то­ду.

decode_tag_form(0) -> primitive;

decode_tag_form(1) -> constructed.

Из­вле­чение иден­ти­фи­ка­то­ра ти­па дан­ных, ко­то­рый име­ет про­из­воль­ную дли­ну – бо­лее слож­ная за­да­ча (про­стой слу­чай, когда зна­чение иден­ти­фи­ка­то­ра ти­па дан­ных со­дер­жит­ся в сег­мен­те раз­ме­ром 5 бит, мы рас­смат­ри­ва­ем от­дель­но). Как мы помним, при ко­ди­ро­вании иден­ти­фи­ка­то­ра мы пре­об­ра­зу­ем его зна­чение в би­то­вую стро­ку, со­стоя­щую из це­ло­го чис­ла сег­мен­тов раз­ме­ром 7 бит. По­сле это­го ка­ж­дый 7-бит­ный сег­мент пре­об­ра­зу­ем в ок­тет (8-бит­ный сег­мент), до­бав­ляя в ка­че­­ст­ве стар­ше­го би­та 1 для всех сег­мен­тов, кро­ме по­следнего, и 0 для по­следнего сег­мен­та. По­это­му при де­ко­ди­ро­вании мы бу­дем по­сту­пать сле­дую­щим об­ра­зом: брать оче­ред­ной ок­тет (8-бит­ный сег­мент), из­вле­кать млад­шие 7 бит и до­бав­лять к спи­ску 7-бит­ных сег­мен­тов. Де­лать эту опе­ра­цию мы бу­дем до тех пор, по­ка нам не встре­тит­ся ок­тет, стар­ший бит у ко­то­ро­го ра­вен 0. По­сле это­го мы ме­ня­ем по­ря­док сег­мен­тов в спи­ске на об­рат­ный (т. к. по со­об­ра­жениям про­из­во­ди­тель­но­сти мы до­бав­ля­ем сег­мен­ты в конец спи­ска), пре­об­ра­зу­ем спи­сок сег­мен­тов в би­то­вую стро­ку и из­вле­ка­ем из по­лу­чен­ной би­то­вой стро­ки це­лое чис­ло.

decode_tag_value(<<0:1, Segment:7, Rest/binary>>, SegmentList) ->

TagValueBitstring = list_to_bitstring(lists:reverse([<<Segment:7>>] ++ SegmentList)),

BitSize = bit_size(TagValueBitstring),

<<TagValue:BitSize/integer-big>> = TagValueBitstring,

{TagValue, Rest};

decode_tag_value(<<1:1, Segment:7, Rest/binary>>, SegmentList) ->

decode_tag_value(Rest, [<<Segment:7>>] ++ SegmentList).

Сле­дую­щий, не менее необ­хо­ди­мый шаг – это де­ко­ди­ро­вание дли­ны (ко­ли­че­­ст­ва ок­те­тов), ко­то­рую занима­ют дан­ные. Да­вай­те вспомним, как мы ко­ди­ру­ем дли­ну, занимае­мую дан­ны­ми. Ес­ли зна­чение дли­ны мень­ше 128, то для хранения доста­точ­но од­но­го ок­те­та (сле­ду­ет от­ме­тить, что при этом у ок­те­та со зна­чением дли­ны стар­ший бит бу­дет ра­вен 0). Ес­ли зна­чение дли­ны боль­ше или рав­но 128, то пер­вым идет ок­тет, у ко­то­ро­го стар­ший бит уста­нов­лен в 1, а осталь­ные би­ты со­дер­жат ко­ли­че­­ст­во ок­те­тов для хранения дли­ны, по­сле че­го идет са­ма дли­на (занимаю­щая це­лое чис­ло ок­те­тов). Функ­цио­наль­ность по де­ко­ди­ро­ванию дли­ны реа­ли­зу­ет функ­ция decode_length/1; вполне ло­гич­но, что она со­дер­жит два ва­ри­ан­та, по­кры­ваю­щих два воз­мож­ных слу­чая хранения дли­ны в за­ко­ди­ро­ван­ном ви­де.

decode_length(<<0:1, Length:7, Rest/binary>>) -> {Length, Rest};

decode_length(<<1:1, LengthOctetCount:7, Rest/binary>>) ->

LengthBitCount = 8 * LengthOctetCount,

<<Length:LengthBitCount, ParseRest/binary>> = Rest,

{Length, ParseRest}.

Стан­дарт­ные ти­пы дан­ных ASN.1

Стан­дарт­ные ти­пы дан­ных ASN.1 – это ти­пы дан­ных, опи­сан­ные в стан­дар­тах ASN.1. У этих ти­пов дан­ных класс universal; ра­бо­тать с дан­ны­ми стан­дарт­ных ти­пов долж­на уметь лю­бая ASN.1-со­вмес­ти­мая реа­ли­за­ция про­то­ко­ла. К стан­дарт­ным ти­пам дан­ных от­но­сят­ся сле­дую­щие: » Ло­ги­че­­ские зна­чения (BOOLEAN) Принима­ют два зна­чения – TRUE и FALSE.

» Зна­чение NULL Соб­ст­вен­но го­во­ря, это не тип, а спе­ци­аль­ное зна­чение, об­ра­ба­ты­вае­мое и ко­ди­руе­мое спе­ци­аль­ным, от­лич­ным от лю­бых дру­гих зна­чений лю­бых ти­пов спо­со­бом.

» Це­лые чис­ла (INTEGER) Со­дер­жит це­лые чис­ла про­из­воль­но­го раз­ме­ра.

» Пе­ре­чис­ления (ENUMERATED) Под­мно­же­ст­во це­лых чи­сел; с точ­ки зрения пра­вил ко­ди­ро­вания ASN.1 BER, зна­чения это­го ти­па ко­ди­ру­ют­ся совершенно так же, как и це­лые чис­ла.

» Дей­ст­ви­тель­ные чис­ла (REAL) Со­дер­жит дей­ст­ви­тель­ные чис­ла про­из­воль­но­го раз­ме­ра. С точ­ки зрения пра­вил ко­ди­ро­вания ASN.1 BER дей­ст­ви­тель­ные чис­ла по осно­ванию 10 и по осно­ванию 2 ко­ди­ру­ют­ся по-раз­но­му. Пра­ви­ла ко­ди­ро­вания ASN.1 DER оп­ре­де­ля­ют один фор­мат ко­ди­ро­вания для дей­ст­ви­тель­ных чи­сел – как по осно­ванию 10, так и по осно­ванию 2.

» Би­то­вые стро­ки (BIT STRING) Со­дер­жат по­сле­до­ва­тель­ность бит, ко­ли­че­­ст­во ко­то­рых не крат­но 8.

» Стро­ки ок­те­тов (OCTET STRING) Со­дер­жат по­сле­до­ва­тель­ность ок­те­тов (или, что, то же са­мое, по­сле­до­ва­тель­ность бит, ко­ли­че­­ст­во ко­то­рых крат­но 8).

» Иден­ти­фи­ка­тор объ­ек­тов (OBJECT IDENTIFIER и RELATIVE-OID).

» По­сле­до­ва­тель­ность объ­ек­тов (SEQUENCE и SEQUENCE OF) Тип дан­ных для хранения объ­ек­тов дру­гих ти­пов в оп­ре­де­лен­ной по­сле­до­ва­тель­но­сти. Разница ме­ж­ду SEQUENCE и SEQUENCE OF в том, что в по­сле­до­ва­тель­но­стях пер­во­го ти­па до­пуска­ет­ся хранение дан­ных раз­ных ти­пов, тогда как в по­сле­до­ва­тель­но­стях вто­ро­го ти­па до­пуска­ет­ся хранение дан­ных од­но­го ти­па. С точ­ки зрения пра­вил ко­ди­ро­вания ASN.1 BER, SEQUENCE и SEQUENCE OF одинаковы.

» Мно­же­ст­во объ­ек­тов (SET и SET OF) Тип дан­ных для хранения объ­ек­тов дру­гих ти­пов, при этом по­ря­док хранения не оп­ре­де­лен. Разница ме­ж­ду SET и SET OF в том, что во мно­же­ст­вах пер­во­го ти­па до­пуска­ет­ся хранение дан­ных раз­ных ти­пов, тогда как во мно­же­ст­вах вто­ро­го ти­па до­пуска­ет­ся хранение дан­ных од­но­го ти­па. С точ­ки зрения пра­вил ко­ди­ро­вания ASN.1 BER SET и SET OF одинаковы.

Те­перь пе­рей­дем непо­сред­ст­вен­но к де­ко­ди­ро­ванию объ­ек­тов Erlang. Начнем с де­ко­ди­ро­вания бу­лев­ских зна­чений (бу­лев­ские зна­чения в язы­ке Erlang яв­ля­ют­ся не осо­бым ти­пом, а дву­мя пред­о­пре­де­лен­ны­ми ато­ма­ми true и false). Тип дан­ных для бу­лев­ских зна­чений ра­вен 1 (класс – universal, фор­ма – primitive, иден­ти­фи­ка­тор – 1), для хранения соб­ст­вен­но зна­чения доста­точ­но 1 ок­те­та. Ес­ли за­ко­ди­ро­ван­ное зна­чение рав­но 0, то со­от­вет­ст­вую­щее бу­лев­ское зна­чение рав­но false; ес­ли нет, то true.

decode_boolean(<<1:8, 1:8, Value:8, Rest/binary>>, _DecodeDispatcher) ->

if

Value == 0 -> {false, Rest};

Value /= 0 -> {true, Rest}

end.

Еще один тип дан­ных, ко­то­рые мы хо­тим нау­чить­ся де­ко­ди­ро­вать – це­лые чис­ла. Тип дан­ных для бу­лев­ских зна­чений ра­вен 2 (класс – universal, фор­ма – primitive, иден­ти­фи­ка­тор – 2); дли­на, необ­хо­ди­мая для хранения за­ко­ди­ро­ван­ных дан­ных (в от­ли­чие от пре­ды­ду­ще­го слу­чая), мо­жет быть лю­бой. По­это­му, пре­ж­де чем де­ко­ди­ро­вать дан­ные, мы долж­ны по­лу­чить дли­ну (при по­мо­щи функ­ции decode_length/1), по­сле че­го смо­жем де­ко­ди­ро­вать це­лое чис­ло при по­мо­щи опе­ра­ции со­от­вет­ст­вия шаб­ло­ну (для это­го нам нуж­но знать ко­ли­че­­ст­во бит, ко­то­рые занима­ет це­лое чис­ло в би­то­вой стро­ке).

decode_integer(<<2:8, Rest/binary>>, _DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

Length = OctetCount * 8,

<<Number:Length/integer-signed-big, ParseRest/binary>> = OctetCountRest,

{Number, ParseRest}.

Да­вай­те пой­дем даль­ше: рас­смот­рим, как мы бу­дем де­ко­ди­ро­вать дей­ст­ви­тель­ные чис­ла. Хранение дей­ст­ви­тель­ных чи­сел в за­ко­ди­ро­ван­ном ви­де доста­точ­но слож­но, несмот­ря на то, что са­ми зна­чения хра­нят­ся в ви­де строк. Тип дан­ных для дей­ст­ви­тель­ных чи­сел ра­вен 9 (класс – universal, фор­ма – primitive, иден­ти­фи­ка­тор – 9). Ес­ли дли­на за­ко­ди­ро­ван­ных дан­ных рав­но 0, то это оз­на­ча­ет, что мы за­ко­ди­ро­ва­ли дей­ст­ви­тель­ное чис­ло 0.0. Ес­ли дли­на за­ко­ди­ро­ван­ных дан­ных рав­на 1 (т. е. данные занима­ют один ок­тет), а по­сле дли­ны идет ок­тет со зна­чением 2#01000000, то это оз­на­ча­ет, что мы за­ко­ди­ро­ва­ли дей­ст­ви­тель­ное чис­ло +∞ (в язы­ке Erlang для это­го мы ис­поль­зу­ем са­мое боль­шое дей­ст­ви­тель­ное чис­ло 1.7976931348623157*10308). Ес­ли дли­на за­ко­ди­ро­ван­ных дан­ных рав­на 1 (т. е. один ок­тет), а по­сле дли­ны идет ок­тет со зна­чением 2#01000001, то это оз­на­ча­ет, что мы за­ко­ди­ро­ва­ли дей­ст­ви­тель­ное чис­ло -∞ (в язы­ке Erlang для это­го мы ис­поль­зу­ем са­мое ма­лень­кое дей­ст­ви­тель­ное чис­ло -1.7976931348623157*10308). И, на­конец, ес­ли дли­на за­ко­ди­ро­ван­ных дан­ных боль­ше 1, то это оз­на­ча­ет, что нам необ­хо­ди­мо де­ко­ди­ро­вать дан­ные даль­ше.

То, как мы бу­дем де­ко­ди­ро­вать дан­ные даль­ше, за­ви­сит от ок­те­та, ко­то­рый идет по­сле за­ко­ди­ро­ван­ной дли­ны (2 стар­ших би­та это­го ок­те­та рав­ны 0, а остав­шие­ся 6 бит на­зы­ва­ют­ся NR). Ес­ли зна­чение NR рав­но 2#000001, то за­ко­ди­ро­ван­ные дан­ные со­дер­жат це­лое чис­ло в стро­ко­вом пред­став­лении. Ес­ли зна­чение NR рав­но 2#000010, то за­ко­ди­ро­ван­ные дан­ные со­дер­жат дей­ст­ви­тель­ное чис­ло с фик­си­ро­ван­ной за­пя­той в стро­ко­вом пред­став­лении. Ес­ли зна­чение NR рав­но 2#000011, то за­ко­ди­ро­ван­ные дан­ные со­дер­жат дей­ст­ви­тель­ное чис­ло с пла­ваю­щей за­пя­той в стро­ко­вом пред­став­лении.

decode_real(<<9:8, 0:8, Rest/binary>>, _DecodeDispatcher) -> {0.0, Rest};

decode_real(<<9:8, 1:8, 2#01000000:8, Rest/binary>>, _DecodeDispatcher) -> {1.7976931348623157e308, Rest};

decode_real(<<9:8, 1:8, 2#01000001:8, Rest/binary>>, _DecodeDispatcher) -> {-1.7976931348623157e308, Rest};

decode_real(<<9:8, Rest/binary>>, _DecodeDispatcher) ->

{TotalOctetCount, OctetCountRest} = decode_length(Rest),

OctetCount = TotalOctetCount - 1,

<<2#00:2, NR:6, RealBinary:OctetCount/binary, ParseRest/binary>> = OctetCountRest,

RealStr = binary_to_list(RealBinary),

case NR of

2#000001 -> {list_to_integer(RealStr) * 1.0, ParseRest};

2#000010 -> {list_to_float(RealStr), ParseRest};

2#000011 -> {list_to_float(RealStr), ParseRest}

end.

А мы пой­дем даль­ше: рас­смот­рим, как де­ко­ди­ро­вать стро­ку ок­те­тов (бай­то­вую стро­ку). Тип дан­ных для стро­ки ок­те­тов ра­вен 4 (класс – universal, фор­ма – primitive, иден­ти­фи­ка­тор – 4). По­сле ти­па дан­ных идет дли­на, а по­сле нее ок­те­ты, со­став­ляю­щие стро­ку ок­те­тов. Для их из­вле­чения мы ис­поль­зу­ем опе­ра­цию со­от­вет­ст­вия шаб­ло­ну (для это­го нам нуж­но знать их ко­ли­че­­ст­во, т. е. дли­ну).

decode_octetstring(<<4:8, Rest/binary>>, _DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

<<Octet:OctetCount/binary, ParseRest/binary>> = OctetCountRest,

{Octet, ParseRest}.

За­да­ча, близ­кая к пре­ды­ду­щей – де­ко­ди­ро­вание би­то­вой стро­ки (стро­ки, в ко­то­рой ко­ли­че­­ст­во бит не крат­но 8). Тип дан­ных для стро­ки ок­те­тов ра­вен 3 (класс – universal, фор­ма – primitive, иден­ти­фи­ка­тор – 3). По­сле ти­па дан­ных идет дли­на, за ко­то­рой идут дан­ные. Т. к. дли­на дан­ных – это ко­ли­че­­ст­во ок­те­тов, необ­хо­ди­мых для их хранения, а ко­ли­че­­ст­во бит в би­то­вой стро­ке не крат­но 8, то нам необ­хо­ди­мо как-то уз­нать раз­мер остат­ка би­то­вой стро­ки, ко­то­рый хранит­ся в по­следнем ок­те­те. Для это­го пер­вый ок­тет за­ко­ди­ро­ван­ных дан­ных со­дер­жит ко­ли­че­­ст­во неис­поль­зуе­мых бит в по­следнем ок­те­те, по­сле че­го рас­по­ла­га­ет­ся са­ма би­то­вая стро­ка. Оче­вид­но, что в этом слу­чае дли­на, тре­буе­мая для хранения дан­ных, на 1 боль­ше ко­ли­че­­ст­ва ок­те­тов, необ­хо­ди­мых для хранения би­то­вой стро­ки.

decode_bitstring(<<3:8, Rest/binary>>, _DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

<<UnusedBitCount:8, UnusedBitCountRest/binary>> = OctetCountRest,

BitstringLength = 8 * (OctetCount - 1) - UnusedBitCount,

<<Bitstring:BitstringLength/bitstring, _UnusedBits:UnusedBitCount, ParseRest/binary>> = UnusedBitCountRest,

{Bitstring, ParseRest}.

Зай­мемся де­ко­ди­ро­ванием со­став­ных ти­пов дан­ных. Начнем с де­ко­ди­ро­вания по­сле­до­ва­тель­но­стей (или спи­сков). Для них зна­чение ти­па дан­ных рав­ня­ет­ся 48 (класс – universal, фор­ма – constructed, иден­ти­фи­ка­тор – 16). По­сле де­ко­ди­ро­вания ти­па дан­ных мы по­лу­ча­ем дли­ну за­ко­ди­ро­ван­ной по­сле­до­ва­тель­но­сти, по­сле че­го стро­ку-ок­тет по­лу­чен­ной дли­ны мы де­ко­ди­ру­ем так же, как ис­ход­ную би­то­вую стро­ку. Имен­но для это­го слу­чая мы и пе­ре­да­ем в функ­ции де­ко­ди­ро­вания от­дель­ных ти­пов дан­ных функ­цию-дис­пет­чер. Де­ко­ди­ро­вание со­дер­жи­мо­го по­сле­до­ва­тель­но­сти осу­ще­ст­в­ля­ет­ся в функ­ции decode_sequence_content/3.

decode_sequence(<<0:2, 1:1, 16:5, Rest/binary>>, DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

<<SequenceBinary:OctetCount/binary, SequenceRest/binary>> = OctetCountRest,

Sequence = decode_sequence_content(SequenceBinary, DecodeDispatcher, []),

{Sequence, SequenceRest}.

Де­ко­ди­ро­вание кор­те­жей осу­ще­ст­в­ля­ет­ся по тем же са­мым прин­ци­пам, что и де­ко­ди­ро­вание по­сле­до­ва­тель­но­стей. Для кор­те­жей зна­чение ти­па дан­ных рав­ня­ет­ся 16160 = 2#0011111100100000 (класс – universal, фор­ма – constructed, иден­ти­фи­ка­тор – 32). От­ли­чие этой функ­ции от пре­ды­ду­щей за­клю­ча­ет­ся в том, что по­сле де­ко­ди­ро­вания по­лу­чен­ную по­сле­до­ва­тель­ность (или спи­сок) мы пре­об­ра­зу­ем в кор­теж.

decode_tuple(<<0:2, 1:1, 2#11111:5, 32:8, Rest/binary>>, DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

<<SequenceBinary:OctetCount/binary, SequenceRest/binary>> = OctetCountRest,

Sequence = decode_sequence_content(SequenceBinary, DecodeDispatcher, []),

{list_to_tuple(Sequence), SequenceRest}.

Ме­тод decode_sequence_content/3 де­ко­ди­ру­ет по­сле­до­ва­тель­ность ок­те­тов, ко­то­рую мы рас­по­зна­ли как по­сле­до­ва­тель­ность. Для это­го дан­ный ме­тод ис­поль­зу­ет пе­ре­да­вае­мую ему че­рез один из па­ра­мет­ров функ­цию-дис­пет­чер и ме­тод decode/2. Мо­жет возник­нуть сле­дую­щий во­прос: мы из­на­чаль­но вы­зы­ва­ем функ­цию decode/2 для де­ко­ди­ро­вания дан­ных, и в ре­зуль­та­те это­го де­ко­ди­ро­вания мы сно­ва вы­зы­ва­ем функ­цию decode/2; не при­ве­дет ли это к бес­конеч­ной ре­кур­сии? Но ес­ли хо­ро­шо по­ду­мать, то мож­но от­ве­тить, что нет, т. к., мы вся­кий раз вы­зы­ва­ем функ­цию decode/2 для би­то­вой стро­ки, ко­то­рая мень­ше ис­ход­ной.

decode_sequence_content(<<>>, _DecodeDispatcher, ContentList) ->

lists:reverse(ContentList);

decode_sequence_content(Binary, DecodeDispatcher, ContentList) ->

{DecodedElement, DecodeRest} = decode(Binary, DecodeDispatcher),

decode_sequence_content(DecodeRest, DecodeDispatcher, [DecodedElement] ++ ContentList).

И, на­конец, по­следний ме­тод для де­ко­ди­ро­вания дан­ных ра­бо­та­ет с за­ко­ди­ро­ван­ны­ми ато­ма­ми. Для ато­мов зна­чение ти­па дан­ных рав­ня­ет­ся 16161 = 2#0011111100100001 (класс – universal, фор­ма – constructed, иден­ти­фи­ка­тор – 33). Во всем осталь­ном этот ме­тод три­виа­лен.

decode_atom(<<0:2, 0:1, 2#11111:5, 33:8, Rest/binary>>, _DecodeDispatcher) ->

{OctetCount, OctetCountRest} = decode_length(Rest),

<<AtomBinary:OctetCount/binary, ParseRest/binary>> = OctetCountRest,

{binary_to_atom(AtomBinary, utf8), ParseRest}.

Наш при­мер по ко­ди­ро­ванию и де­ко­ди­ро­ванию объ­ек­тов Erlang в со­от­вет­ст­вии с пра­ви­ла­ми ASN.1 BER за­кон­чен. Ос­та­лось толь­ко про­ве­рить, что все ра­бо­та­ет. Ес­ли под­хо­дить к та­кой про­вер­ке пра­виль­но, то необ­хо­ди­мо убе­дить­ся, что ка­ж­дый тип под­дер­жи­вае­мых объ­ек­тов ко­ди­ру­ет­ся и де­ко­ди­ру­ет­ся на­ши­ми мо­ду­ля­ми долж­ным об­ра­зом (дан­ные про­вер­ки удоб­но реа­ли­зо­вать при по­мо­щи unit-тес­тов; мы по­го­во­рим о unit-тес­ти­ро­вании при­ло­жений для Erlang в од­ной из бу­ду­щих ста­тей). Кро­ме то­го, мы помним, что ASN.1 – это стан­дарт взаи­мо­дей­ст­вия раз­лич­ных при­ло­жений, на­пи­сан­ных на раз­ных язы­ках и под раз­ные плат­фор­мы. По­это­му необ­хо­ди­мым ша­гом про­вер­ки бу­дет про­вер­ка взаи­мо­дей­ст­вия на­ше­го при­ло­жения со сто­ронним при­ло­жением: дан­ные, за­ко­ди­ро­ван­ные на­шим при­ло­жением, долж­ны быть рас­ко­ди­ро­ва­ны сто­ронним, и на­обо­рот (за неко­то­ры­ми ис­клю­чения­ми, о ко­то­рых мы по­го­во­рим в за­клю­чении). Од­на­ко из-за ог­раничения мес­та под ста­тью ав­тор при­ве­дет лишь при­мер, что слож­ная струк­ту­ра дан­ных Erlang по­сле ко­ди­ро­вания и де­ко­ди­ро­вания не ме­ня­ет­ся; это мож­но счи­тать непло­хим smoke-тес­том. Что мы де­ла­ем: за­пуска­ем сре­ду вы­полнения Erlang, ком­пи­ли­ру­ем мо­ду­ли asn1_encoder и asn1_decoder, соз­да­ем функ­ции-дис­пет­че­ры для ко­ди­ро­вания и де­ко­ди­ро­вания, по­сле че­го ко­ди­ру­ем неко­то­рый слож­ный объ­ект и де­ко­ди­ру­ем его. Ес­ли все ра­бо­та­ет пра­виль­но, то по­сле де­ко­ди­ро­вания мы долж­ны по­лу­чить точ­но та­кой же объ­ект, как до ко­ди­ро­вания.

c(asn1_encoder).

c(asn1_decoder).

Source = {abc, 12, 3.14, [], [{ab, true}, <<1:8>>, <<1:7>>], {}}.

Encoder = asn1_encoder:build([]).

Decoder = asn1_decoder:build([]).

Data = asn1_encoder:encode(Source, Encoder).

{Dest, Rest} = asn1_decoder:decode(Data, Decoder).

По­сле вы­полнения всех вы­ше­при­ве­ден­ных команд мы по­лу­ча­ем, что пе­ре­мен­ная Dest (объ­ект по­сле ко­ди­ро­вания и де­ко­ди­ро­вания) со­дер­жит точ­но та­кой же объ­ект, что и Source, а пе­ре­мен­ная Rest – пустую би­то­вую стро­ку (что ло­гич­но, ибо у нас не долж­но остать­ся не де­ко­ди­ро­ван­ных дан­ных). Мож­но счи­тать, что наш при­мер про­шел smoke-тест.

Мы за­кон­чи­ли боль­шой при­мер по ис­поль­зо­ванию би­то­вых строк. Про на­шу реа­ли­за­цию ко­ди­ро­вания и де­ко­ди­ро­вания в со­от­вет­ст­вии с пра­ви­ла­ми ASN.1 BER мож­но ска­зать сле­дую­щее: мы не реа­ли­зо­ва­ли под­держ­ку всех стан­дарт­ных ти­пов ASN.1 (мы не под­дер­жи­ва­ем, на­при­мер, иден­ти­фи­ка­то­ры, стро­ки, зна­чение NULL) и мы вве­ли спе­ци­фич­ные толь­ко для язы­ка Erlang ти­пы (это кор­те­жи и ато­мы). Что ка­са­ет­ся пер­во­го за­ме­чания, то мы ис­хо­ди­ли из це­лей по­стро­ить сис­те­му, доста­точ­ную для при­ме­ра, а не для ре­аль­но­го ис­поль­зо­вания (и, как ка­жет­ся ав­то­ру, это по­лу­чи­лось). Что же ка­са­ет­ся вто­ро­го за­ме­чания, то вполне нор­маль­ная прак­ти­ка, когда мы по­ми­мо стан­дарт­ных ти­пов вво­дим спе­ци­фич­ные и из­вест­ные толь­ко для на­ше­го при­ло­жения.

На этом, по­жа­луй, мож­но по­ста­вить точ­ку. Мы за­вер­ша­ем цикл ста­тей, по­свя­щен­ный прак­ти­ку­му по функ­цио­наль­но­му про­грам­ми­ро­ва­нию в язы­ке Erlang. В сле­дую­щем но­ме­ре мы приступим к раз­го­вору про мно­го­за­дач­ность и по­строе­ние рас­пре­де­лен­ных сис­тем. |

Персональные инструменты
купить
подписаться
Яндекс.Метрика