3. Getting Started

3入门

3.1例

以下示例演示用于运行Erlang ASN.1编译器的基本功能。

创建一个名为People.asn包含以下内容的文件:

People DEFINITIONS AUTOMATIC TAGS ::= BEGIN Person ::= SEQUENCE { name PrintableString, location INTEGER {home(0),field(1),roving(2)}, age INTEGER OPTIONAL } END

该文件必须在可以使用之前进行编译。ASN.1编译器检查语法是否正确,并且在生成抽象语法树之前,文本表示正确的ASN.1代码。代码生成器然后使用抽象语法树来生成代码。

生成的Erlang文件放置在当前目录或用选项指定的目录中{outdir,Dir}

以下显示了如何从Erlang shell中调用编译器:

1>asn1ct:compile("People", [ber]). ok 2>

verbose可以添加选项以获取有关生成的文件的信息:

2>asn1ct:compile("People", [ber,verbose]). Erlang ASN.1 compiling "People.asn" --{generated,"People.asn1db"}-- --{generated,"People.hrl"}-- --{generated,"People.erl"}-- ok 3>

People现在接受ASN.1模块,并将抽象语法树保存在文件中People.asn1db。生成的Erlang代码使用Erlang编译器编译并加载到Erlang运行时系统中。现在有一个用于encode/2decode/2在模块中的API People,它被称为:

'People':encode(<Type name>, <Value>)

'People':decode(<Type name>, <Value>)

假设有一个网络应用程序接收ASN.1定义的类型的实例Person,修改并再次发回它们:

receive {Port,{data,Bytes}} -> case 'People':decode('Person',Bytes) of {ok,P} -> {ok,Answer} = 'People':encode('Person',mk_answer(P)), Port ! {self(),{command,Answer}}; {error,Reason} -> exit{error,Reason}) end end,

在这个例子中,从外部源接收一系列字节,然后字节被解码为一个有效的Erlang项。这是通过调用实现的,该调用'People':decode('Person',Bytes)返回了ASN.1类型的Erlang值Person。然后使用'People':encode('Person',Answer)这种方法构造并编码答案,该答案采用已定义的ASN.1类型的实例,并根据BER或PER编码规则将其转换为二进制。

编码器和解码器也可以从外壳运行:

2> Rockstar = {'Person',"Some Name",roving,50}. {'Person',"Some Name",roving,50} 3> {ok,Bin} = 'People':encode('Person',Rockstar). {ok,<<243,17,19,9,83,111,109,101,32,78,97,109,101,2,1,2, 2,1,50>>} 4> {ok,Person} = 'People':decode('Person',Bin). {ok,{'Person',"Some Name",roving,50}} 5>

模块依赖

ASN.1模块通常从另一个ASN.1模块导入已定义的类型,值和其他实体。

早期版本的ASN.1编译器要求导入的模块必须在导入的模块之前编译。当ASN.1模块具有循环依赖关系时,这会造成问题。

当编译器找到一个导入的实体时,现在就会分析引用的模块。没有为引用的模块生成代码。但是,编译后的模块依赖于所引用的模块也被编译。

3.2 ASN.1应用程序用户界面

ASN.1应用程序提供以下两个单独的用户界面:

  • 该模块asn1ct提供编译时功能(包括编译器)

  • 该模块asn1rt_nif为BER后端的ASN.1解码器提供运行时功能

将接口划分为编译时和运行时的原因是,只有运行时模块(asn1rt*)需要在嵌入式系统中加载。

编译时函数

ASN.1编译器可以通过erlc程序直接从命令行启动。从命令行编译许多ASN.1文件或使用Makefile时,这很方便。有关如何使用此erlc命令启动ASN.1编译器的一些示例:

erlc Person.asn erlc -bper Person.asn erlc -bber ../Example.asn erlc -o ../asnfiles -I ../asnfiles -I /usr/local/standards/asn1 Person.asn

ASN.1编译器的有用选项:

-b[ber | per | uper]

编码规则的选择。如果省略,ber则是默认值。

-o OutDirectory

放置生成文件的位置。默认是当前目录。

-I IncludeDir

在哪里搜索.asn1db文件和ASN.1源规范来解析对其他模块的引用。如果要搜索多个位置,则此选项可以重复多次。编译器首先搜索当前目录。

+der

DER编码规则。仅在使用选项时-ber

+maps

使用地图而不是记录来表示SEQUENCESET类型。.hrl将不会生成任何文件。请参阅本节Map representation for SEQUENCE and SET了解更多信息。

+asn1config

该功能与选件一起使用ber。它启用专门的解码,参见章节Specialized Decode

+undec_rest

保存正在解码的消息的缓冲区也可以具有尾随字节。如果这些尾随字节很重要,可以通过编译带有选项的ASN.1规范将它们与解码值一起返回+undec_rest。来自解码器的返回值是{ok,Value,Rest}其中Rest是包含尾随字节的二进制。

+'Any Erlc Option'

编译生成的Erlang文件时,可以将任何选项添加到Erlang编译器中。ASN.1编译器无法识别的任何选项都会传递给Erlang编译器。

有关完整的描述erlc,请参阅ERTS参考手册。

编译器和其他编译时函数也可以从Erlang shell启动。下面简要介绍主要功能。对于每个功能的完整描述,请参阅模块asn1ctASN.1 Reference Manual

编译器由asn1ct:compile/1默认选项启动,或者asn1ct:compile/2如果给出明确的选项。

例子:

asn1ct:compile("H323-MESSAGES.asn1").

这等于:

asn1ct:compile("H323-MESSAGES.asn1",[ber]).

如果需要每种编码:

asn1ct:compile("H323-MESSAGES.asn1",[per]).

通用编码和解码函数可以被调用如下:

'H323-MESSAGES':encode('SomeChoiceType',{call,<<"octetstring">>}). 'H323-MESSAGES':decode('SomeChoiceType',Bytes).

运行时函数

当ASN.1规范与选项编译berasn1rt_nif模块,并在NIF库asn1/priv_dir在运行时是必要的。

通过调用info/0生成模块中的函数,您可以获得有关使用哪个编译器选项的信息。

错误

在编译时检测到的错误与显示在源文件中检测到相应错误的行号一起显示在屏幕上。如果没有发现错误,则创建一个Erlang ASN.1模块。

运行时的编码器和解码器执行的catch内并返回{ok, Data}{error, {asn1, Description}}其中Description是描述错误的Erlang项。

目前,Description看起来像这样:{ErrorDescription, StackTrace}。应用程序不应取决于Description将来可能更改的确切内容。

3.3多文件编译

使用多文件编译有多种原因:

  • 例如,为生成的模块选择名称,因为您需要针对不同的编码规则编译相同的规格。

  • 您只需要一个结果模块。

指定在扩展模块中编译哪些ASN.1规范.set.asn。选择一个模块名称并提供ASN.1规格的名称。例如,如果你有规格File1.asnFile2.asn以及File3.asn,你的模块MyModule.set.asn如下所示:

File1.asn File2.asn File3.asn

如果使用以下语言编译,结果将是一个合并模块MyModule.erl,其中包含三个ASN.1规范生成的代码:

~> erlc MyModule.set.asn

3.4关于标签的注记

对于ASN.1的所有用户来说,标签过去都很重要,因为为了使ASN.1规范有效,有必要手动将标签添加到特定的结构中。旧式规格的例子:

Tags DEFINITIONS ::= BEGIN Afters ::= CHOICE { cheese [0] IA5String, dessert [1] IA5String } END

没有标签(方括号中的数字),ASN.1编译器拒绝编译该文件。

1994 AUTOMATIC TAGS年引入全球标签模式。通过放入AUTOMATIC TAGS模块头,ASN.1编译器会在需要时自动添加标签。以下是AUTOMATIC TAGS模式中的相同规格:

Tags DEFINITIONS AUTOMATIC TAGS ::= BEGIN Afters ::= CHOICE { cheese IA5String, dessert IA5String } END

本用户指南中不再提及标签。

3.5 ASN.1型

本节介绍ASN.1类型,包括它们的功能,用途以及如何在Erlang中分配值。

ASN.1具有原始类型和构造类型:

Primitive TypesConstructed Types
BOOLEANSEQUENCE
INTEGERSET
REALCHOICE
NULLSET OF and SEQUENCE OF
ENUMERATEDANY
BIT STRINGANY DEFINED BY
OCTET STRINGEXTERNAL
Character StringsEMBEDDED PDV
OBJECT IDENTIFIERCHARACTER STRING
Object Descriptor
TIME Types

每个ASN.1类型的值在Erlang中都有自己的表示,如以下各节所述。用户必须根据表示提供这些值进行编码,如以下示例所示:

Operational ::= BOOLEAN --ASN.1 definition

在Erlang代码中,它可以如下所示:

Val = true, {ok,Bytes} = MyModule:encode('Operational', Val),

布尔型

在ASN.1中的布尔表达值可以是TRUE或者FALSE。分配给的含义TRUEFALSE在本文的范围之外。

在ASN.1中,有可能有:

Operational ::= BOOLEAN

Operational通过使用以下Erlang代码,可以指定一个值来输入Erlang:

Myvar1 = true,

因此,在Erlang原子truefalse用于编码一个布尔值。

整型

ASN.1本身规定了无限大的整数。具有4.3及更高版本的Erlang系统支持非常大的整数,实际上是无限大的整数。

子类型的概念可以应用于整数和其他ASN.1类型。子类型的细节在这里没有解释; 有关更多信息,请参阅X.680。将类型定义为整数时允许使用各种语法:

T1 ::= INTEGER T2 ::= INTEGER (-2..7) T3 ::= INTEGER (0..MAX) T4 ::= INTEGER (0<..MAX) T5 ::= INTEGER (MIN<..-99) T6 ::= INTEGER {red(0),blue(1),white(2)}

INTEGER如果指定了a Named Number List(参见T6前面的列表),则ASN.1的Erlang表示是整数或原子。

以下是为前一列表中的类型指定值的Erlang代码示例:

T1value = 0, T2value = 6, T6value1 = blue, T6value2 = 0, T6value3 = white

这些Erlang变量现在绑定到ASN.1定义类型的有效实例。这种类型的值可以直接传递给编码器以转换为一系列字节。

解码器返回一个原子,如果该值对应于中的符号Named Number List

以下ASN.1类型用于实数:

R1 ::= REAL

它在Erlang中的赋值如下:

R1value1 = "2.14", R1value2 = {256,10,-2},

在最后一行中,请注意元组{256,10,-2}是一个特殊符号的实数2.56,它的编码速度比简单地将数字表示为"2.56"。三元组是三元组{Mantissa,Base,Exponent},即Mantissa * Base ^ Exponent。

该类型NULL适用于供应和识别价值很重要但实际价值不重要的情况。

Notype ::= NULL

这种类型在Erlang中分配如下:

N1 = 'NULL',

实际值是引用的原子'NULL'

经点算

ENUMERATED当您想要描述的值只能使用一组预定义值时,可以使用该类型。例:

DaysOfTheWeek ::= ENUMERATED { sunday(1),monday(2),tuesday(3), wednesday(4),thursday(5),friday(6),saturday(7) }

例如,要在Erlang中指定周日值,请使用与Enumerations类型定义中相同的原子:

Day1 = saturday,

枚举类型与整数类型相似,用一组预定义值进行定义。区别在于枚举类型只能有指定的值,而整数可以有任何值。

位串

该类型BIT STRING可用于建模由任意长度的一系列比特组成的信息。它旨在用于选择标志,而不是二进制文件。

在ASN.1中,BIT STRING定义可以如下所示:

Bits1 ::= BIT STRING Bits2 ::= BIT STRING {foo(0),bar(1),gnu(2),gnome(3),punk(14)}

以下两种符号可用于表示BIT STRINGErlang中的值以及作为编码函数的输入:

  • 比特串。默认情况下,BIT STRING没有符号名称的解码为Erlang位串。

  • NamedBitListBIT STRING定义中的原子相对应的原子列表。BIT STRING具有符号名称的A 总是被解码为以下示例中所示的格式:

Bits1Val1 = <<0:1,1:1,0:1,1:1,1:1>>, Bits2Val1 = [gnu,punk], Bits2Val2 = <<2#1110:4>>, Bits2Val3 = [bar,gnu,gnome],

Bits2Val2Bits2Val3表示相同的值。

Bits2Val1被分配符号值。分配意味着对应的位gnupunk,也就是第2位和14都设置为1,其余的被设置为0的符号值显示为值的列表。如果显示未在类型定义中指定的命名值,则会发生运行时错误。

BIT STRING也可以用例如,SIZE规格:

Bits3 ::= BIT STRING (SIZE(0..31))

这意味着不能设置高于31的位。

位字符串的不推荐表示

除了前面所述的表示外,如果规范已使用选项进行编译,则可以使用以下不推荐使用的表示legacy_erlang_types

  • Aa二进制数字列表(0或1)。该格式被接受为编码功能的输入,并且BIT STRING如果给出选项legacy_bit_string,则将 a 解码为该格式。

  • {Unused,Binary}Unused表示在最低有效字节中有多少尾随零位0-7未使用Binary。这种格式被接受为编码功能的输入,并且BIT STRING如果compact_bit_string已经给出,则a 被解码为这种格式。

  • 作为十六进制数字(或整数)。避免这种情况,因为很容易误解BIT STRING这种格式的值。

八位串

OCTET STRING是所有ASN.1类型中最简单的。OCTET STRING只能移动或传输符合两条规则的二进制文件或其他非结构化信息:字节由八位位组组成,并且不需要编码。

有可能有以下ASN.1类型定义:

O1 ::= OCTET STRING O2 ::= OCTET STRING (SIZE(28))

在Erlang中使用以下示例赋值:

O1Val = <<17,13,19,20,0,0,255,254>>, O2Val = <<"must be exactly 28 chars....">>,

默认情况下,一个OCTET STRING总是表示为Erlang二进制。如果规范是用选项编译的legacy_erlang_types,则编码函数接受列表和二进制文件,解码函数将OCTET STRING列表解码为列表。

字符串

ASN.1支持各种字符集。OCTET STRING一个字符串和一个字符串的主要区别在于,在OCTET STRING传递的字节上没有强加的语义。

但是,例如,在使用IA5String(非常类似于ASCII)时,字节65(十进制表示法)表示字符'A'。

例如,如果一个定义的类型是一个VideotexString并且一个八位位组是用无符号整数值接收的,那么X这个八位字节将按照标准ITU-T T.100,T.101中的规定进行解释。

ASN.1到Erlang编译器不能确定每个BER字符串八位字节值对不同字符串的正确解释。该应用程序负责解释八位字节。因此,从BER字符串的角度来看,八位字节与字符串非常相似,并且以相同的方式进行编译。

当使用PER时,OCTET STRINGs和其他字符串之间的编码方案存在显着差异。为类型指定的约束对PER来说尤为重要,因为它们会影响编码。

例子:

Digs ::= NumericString (SIZE(1..3)) TextFile ::= IA5String (SIZE(0..64000))

相应的Erlang作业:

DigsVal1 = "456", DigsVal2 = "123", TextFileVal1 = "abc...xyz...", TextFileVal2 = [88,76,55,44,99,121 .......... a lot of characters here ....]

“BMPString”和“UniversalString”的Erlang表示是ASCII值列表或四元组列表。四倍表示与字符的Unicode标准表示相关联。ASCII字符全部由以字符'A'的{0,0,0,65}三个零开始的四位表示。为这些字符串解码值时,结果是四元组列表,或者当值是ASCII字符时为整数。

以下示例显示了它的工作原理。假设以下规范在文件中PrimStrings.asn1

PrimStrings DEFINITIONS AUTOMATIC TAGS ::= BEGIN BMP ::= BMPString END

编码和解码一些字符串:

1> asn1ct:compile('PrimStrings', [ber]). ok 2> {ok,Bytes1} = 'PrimStrings':encode('BMP', [{0,0,53,53},{0,0,45,56}]). {ok,<<30,4,53,54,45,56>>} 3> 'PrimStrings':decode('BMP', Bytes1). {ok,[{0,0,53,53},{0,0,45,56}]} 4> {ok,Bytes2} = 'PrimStrings':encode('BMP', [{0,0,53,53},{0,0,0,65}]). {ok,<<30,4,53,53,0,65>>} 5> 'PrimStrings':decode('BMP', Bytes2). {ok,[{0,0,53,53},65]} 6> {ok,Bytes3} = 'PrimStrings':encode('BMP', "BMP string"). {ok,<<30,20,0,66,0,77,0,80,0,32,0,115,0,116,0,114,0,105,0,110,0,103>>} 7> 'PrimStrings':decode('BMP', Bytes3). {ok,"BMP string"}

类型UTF8String在Erlang中表示为UTF-8编码二进制。这样的二进制文件可以直接使用二进制语法或使用函数从Unicode代码点列表转换来创建unicode:characters_to_binary/1

以下显示了如何创建和操作UTF-8编码二进制文件的示例:

1> Gs = "Мой маленький Гном". [1052,1086,1081,32,1084,1072,1083,1077,1085,1100,1082,1080, 1081,32,1043,1085,1086,1084] 2> Gbin = unicode:characters_to_binary(Gs). <<208,156,208,190,208,185,32,208,188,208,176,208,187,208, 181,208,189,209,140,208,186,208,184,208,185,32,208,147, 208,...>> 3> Gbin = <<"Мой маленький Гном"/utf8>>. <<208,156,208,190,208,185,32,208,188,208,176,208,187,208, 181,208,189,209,140,208,186,208,184,208,185,32,208,147, 208,...>> 4> Gs = unicode:characters_to_list(Gbin). [1052,1086,1081,32,1084,1072,1083,1077,1085,1100,1082,1080, 1081,32,1043,1085,1086,1084]

有关详细信息,请参阅unicodeSTDLIB中的模块。

在下面的例子中,使用了这个ASN.1规范:

UTF DEFINITIONS AUTOMATIC TAGS ::= BEGIN UTF ::= UTF8String END

使用Unicode字符编码和解码字符串:

5> asn1ct:compile('UTF', [ber]). ok 6> {ok,Bytes1} = 'UTF':encode('UTF', <<"Гном"/utf8>>). {ok,<<12,8,208,147,208,189,208,190,208,188>>} 7> {ok,Bin1} = 'UTF':decode('UTF', Bytes1). {ok,<<208,147,208,189,208,190,208,188>>} 8> io:format("~ts\n", [Bin1]). Гном ok 9> unicode:characters_to_list(Bin1). [1043,1085,1086,1084]

对象标识符

OBJECT IDENTIFIER只要需要唯一标识就会使用该类型。一个ASN.1模块,一个传输语法等等,用一个OBJECT IDENTIFIER。假设下面的例子:

Oid ::= OBJECT IDENTIFIER

因此,以下示例是类型为“Oid”的有效Erlang实例:

OidVal1 = {1,2,55},

OBJECT IDENTIFIER值只是一个连续值的元组,它必须是整数。

第一个值限制为值0,1或2.当第一个值为0或1时,第二个值必须在0..39范围内。

OBJECT IDENTIFIER是一种重要的类型,它被广泛用于不同的标准中以唯一地识别各种物体。Dubuisson:ASN.1 - 异构系统之间的通信包括一个易于理解的使用说明OBJECT IDENTIFIER

对象描述符

这种类型的值可以按如下方式被赋值为普通字符串:

"This is the value of an Object descriptor"

时间类型

ASN.1中定义了两种时间类型:通用时间和协调世界时(UTC)。双引号内的值均为普通字符串,例如“19820102070533.8”。

对于DER编码,编译器不检查时间值的有效性。DER对这些字符串的要求被认为是应用程序要履行的事情。

序列

ASN.1的结构化类型是以类似于C中的数组和结构的概念的方式从其他类型构造而来的。

SEQUENCEASN.1中的A 与C中的结构和Erlang中的记录相当。SEQUENCE可以定义如下:

Pdu ::= SEQUENCE { a INTEGER, b REAL, c OBJECT IDENTIFIER, d NULL }

这是一个叫做4分量结构的结构Pdu。默认情况下,a SEQUENCE由Erlang中的记录表示。它也可以表示为一张地图; 见Map representation for SEQUENCE and SET。对于每个SEQUENCESET一个ASN.1模块中生成一个Erlang记录声明。因为Pdu,定义如下的记录:

-record('Pdu',{a, b, c, d}).

模块的记录声明M被放置在一个单独的M.hrl文件中。

值可以在Erlang中分配如下:

MyPdu = #'Pdu'{a=22,b=77.99,c={0,1,2,3,4},d='NULL'}.

解码函数在解码a SEQUENCE或a 时返回一条记录SET

SEQUENCESET可以包含具有DEFAULT关键字的组件,后面跟着实际值,这是默认值。该DEFAULT关键字意味着执行编码的应用程序可以省略该值的编码,这导致发送到接收应用程序的字节更少。

应用程序可以使用该原子asn1_DEFAULT来指示编码将被忽略的位置SEQUENCE

根据编码规则,编码器还可以将给定值与默认值进行比较,如果值相等,则自动省略编码。编码器比较值的工作量取决于编码规则。DER编码规则禁止编码等于默认值的值,所以它比其他编码规则的编码器具有更彻底和更耗时的比较。

在下面的例子中,使用了这个ASN.1规范:

File DEFINITIONS AUTOMATIC TAGS ::= BEGIN Seq1 ::= SEQUENCE { a INTEGER DEFAULT 1, b Seq2 DEFAULT {aa TRUE, bb 15} } Seq2 ::= SEQUENCE { aa BOOLEAN, bb INTEGER } Seq3 ::= SEQUENCE { bs BIT STRING {a(0), b(1), c(2)} DEFAULT {a, c} } END

BER编码器能够省略默认值编码的示例:

1> asn1ct:compile('File', [ber]). ok 2> 'File':encode('Seq1', {'Seq1',asn1_DEFAULT,asn1_DEFAULT}). {ok,<<48,0>>} 3> 'File':encode('Seq1', {'Seq1',1,{'Seq2',true,15}}). {ok,<<48,0>>}

BIT STRING在BER编码器不省略编码的情况下命名的示例:

4> 'File':encode('Seq3', {'Seq3',asn1_DEFAULT). {ok,<<48,0>>} 5> 'File':encode('Seq3', {'Seq3',<<16#101:3>>). {ok,<<48,4,128,2,5,160>>}

DER编码器省略了相同的编码BIT STRING

6> asn1ct:compile('File', [ber,der]). ok 7> 'File':encode('Seq3', {'Seq3',asn1_DEFAULT). {ok,<<48,0>>} 8> 'File':encode('Seq3', {'Seq3',<<16#101:3>>). {ok,<<48,0>>}

SET集

在Erlang中,SET类型的使用与SEQUENCE。请注意,如果使用BER或DER编码规则,则解码a SET比解码a要慢,SEQUENCE因为必须对组件进行排序。

序列和集合的扩展性

SEQUENCE或者SET包含扩展标记和扩展组件时,该类型可以在更新版本的ASN.1规范中获得更多组件:

SExt ::= SEQUENCE { a INTEGER, ..., b BOOLEAN }

在这种情况下,它有一个新的组件b。因此,被解码的传入消息可能比这个消息有更多或发烧的组件。

b编码消息时,该组件被视为原始组件。在这种情况下,因为它不是可选元素,所以它必须被编码。

在解码期间,b记录的字段获取b组件的解码值(如果存在),否则为值asn1_NOVALUE

序列和集合的映射表示

如果ASN.1模块已经编译选项maps,类型SEQUENCESET被表示为地图。

在以下示例中,使用了本ASN.1规范:

File DEFINITIONS AUTOMATIC TAGS ::= BEGIN Seq1 ::= SEQUENCE { a INTEGER DEFAULT 42, b BOOLEAN OPTIONAL, c IA5String } END

如果没有任何值,可选字段将从地图中省略:

1> asn1ct:compile('File', [per,maps]). ok 2> {ok,E} = 'File':encode('Seq1', #{a=>0,c=>"string"}). {ok,<<128,1,0,6,115,116,114,105,110,103>>}

解码时,映射中将省略可选字段:

3> 'File':decode('Seq1', E). {ok,#{a => 0,c => "string"}}

地图上可以省略默认值:

4> {ok,E2} = 'File':encode('Seq1', #{c=>"string"}). {ok,<<0,6,115,116,114,105,110,103>>} 5> 'File':decode('Seq1', E2). {ok,#{a => 42,c => "string"}}

不允许使用原子asn1_VALUEasn1_DEFAULT地图。

选择

该类型CHOICE是节省空间的,类似于C中“union”的概念。

假设如下:

SomeModuleName DEFINITIONS AUTOMATIC TAGS ::= BEGIN T ::= CHOICE { x REAL, y INTEGER, z OBJECT IDENTIFIER } END

然后可以如下分配值:

TVal1 = {y,17}, TVal2 = {z,{0,1,2}},

一个CHOICE值总是以元组的形式表示,{ChoiceAlternative, Val}其中ChoiceAlternative是一个表示所选择的选择方案的原子。

可扩展选择

CHOICE包含扩展标记并且解码器检测到未知的替代选项时CHOICE,该值表示如下:

{asn1_ExtAlt, BytesForOpenType}

以下BytesForOpenType是构成“未知” CHOICE选项编码的字节列表。

集合和序列

这些类型SET OFSEQUENCE OF几种编程语言中的数组概念相对应。这两种类型的Erlang语法都很简单,例如:

Arr1 ::= SET SIZE (5) OF INTEGER (4..9) Arr2 ::= SEQUENCE OF OCTET STRING

在Erlang可以适用以下内容:

Arr1Val = [4,5,6,7,8], Arr2Val = ["abc",[14,34,54],"Octets"],

请注意,类型的定义SET OF意味着组件的顺序是未定义的,但实际上SET OF和之间没有区别SEQUENCE OF。Erlang的ASN.1编译器SET OF在编码之前不会随机化组件的顺序。

但是,对于类型值SET OF,DER编码格式要求按照编码的升序发送元素,这意味着在运行时需要昂贵的排序过程。因此建议使用,SEQUENCE OF而不是SET OF如果可能的话。

类型ANYANY DEFINED BY已经从自1994年以来,建议不要再使用这些类型的标准中删除。但是,它们可以存在于一些旧的ASN.1模块中。这种类型的想法是在一个定义中留下一个“漏洞”,在这个定义中可以放置任何类型的非特定数据,即使是非ASN.1数据。

这种类型的值被编码为open type

取而代之的ANYANY DEFINED BY,建议使用information object classtable constraintsparameterization。特别是该构造TYPE-IDENTIFIER.@Type完成与已弃用的相同ANY

另见Information object

外部、嵌入式PDV和字符串

表示层协商中使用的类型EXTERNALEMBEDDED PDVCHARACTER STRING。它们根据其相关类型进行编码,参见X.680。

该类型EXTERNAL在1994年之前有一个稍微不同的关联类型。X.691指出编码必须遵循旧的关联类型。因此,生成的编码/解码函数在编码之前将较新格式的值转换为旧格式。这意味着它可以使用EXTERNAL任何格式的类型值进行编码。解码值始终以较新的格式返回。

嵌入式命名类型

前面描述的结构化类型可以具有其他命名类型作为其组件。在Erlang中为C一个已命名的ASN.1类型的组件赋值的一般语法T是记录语法#'T'{'C'=Value}。这Value可以是另一种类型的值T2,例如:

EmbeddedExample DEFINITIONS AUTOMATIC TAGS ::= BEGIN B ::= SEQUENCE { a Arr1, b T } Arr1 ::= SET SIZE (5) OF INTEGER (4..9) T ::= CHOICE { x REAL, y INTEGER, z OBJECT IDENTIFIER } END

SEQUENCEb可以用Erlang编码如下:

1> 'EmbeddedExample':encode('B', {'B',[4,5,6,7,8],{x,"7.77"}}). {ok,<<5,56,0,8,3,55,55,55,46,69,45,50>>}

3.6 .hrl文件中记录的命名

maps给出选项时,不会.hrl生成文件。本节的其余部分描述编译器在maps不使用时的行为。

当ASN.1规范被编译时,所有定义类型的类型SET或者SEQUENCE在生成的.hrl文件中产生相应的记录。这是因为价值观SETSEQUENCE表示为默认的记录。

下一节将介绍此功能的一些特殊情况。

嵌入式结构化类型

在ASN.1中,也可以拥有本身为结构化类型的组件。例如,可以具有以下内容:

Emb ::= SEQUENCE { a SEQUENCE OF OCTET STRING, b SET { a INTEGER, b INTEGER DEFAULT 66}, c CHOICE { a INTEGER, b FooType } } FooType ::= [3] VisibleString

生成下列记录是因为类型Emb*

-record('Emb,{a, b, c}). -record('Emb_b',{a, b = asn1_DEFAULT}). % the embedded SET type

类型值Emb可按以下方式分配:

V = #'Emb'{a=["qqqq",[1,2,255]], b = #'Emb_b'{a=99}, c ={b,"Can you see this"}}.

对于嵌入式类型SEQUENCE/ SETSEQUENCE/中SET,记录名称使用下划线和组件名称进行扩展。如果嵌入式结构与更深SEQUENCESETCHOICE类型的线,每个部件名称/替代名称被添加到记录名称。

例子:

Seq ::= SEQUENCE{ a CHOICE{ b SEQUENCE { c INTEGER } } }

这导致了以下记录:

-record('Seq_a_b',{c}).

如果结构化类型具有嵌入式SEQUENCE OF/ SET OF嵌入式类型的组件依次为SEQUENCE/ SET,则它会为SEQUENCE OF/ SET OF添加记录,如下例所示:

Seq ::= SEQUENCE { a SEQUENCE OF SEQUENCE { b } c SET OF SEQUENCE { d } }

这导致了以下记录:

-record('Seq_a_SEQOF'{b}). -record('Seq_c_SETOF'{d}).

参数化类型将被视为嵌入类型。每次引用这种类型时,都会定义它的一个实例。因此,在以下示例'Seq_b'中,.hrl文件中会生成一个名称为record的记录,并用于保存值:

Seq ::= SEQUENCE { b PType{INTEGER} } PType{T} ::= SEQUENCE{ id T }

递归类型

引用自己的类型称为递归类型。例:

Rec ::= CHOICE { nothing NULL, something SEQUENCE { a INTEGER, b OCTET STRING, c Rec }}

这在ASN.1中是允许的,而ASN.1-to-Erlang编译器支持这种递归类型。这种类型的值在Erlang中分配如下:

V = {something,#'Rec_something'{a = 77, b = "some octets here", c = {nothing,'NULL'}}}.

3.7 ASN.1值

ASN.1代码本身可以将值分配给ASN.1类型,而不是上一节中将值分配给Erlang中的ASN.1类型的操作。支持ASN.1的完整值语法,X.680详细描述了如何在ASN.1中分配值。一个简短的例子:

TT ::= SEQUENCE { a INTEGER, b SET OF OCTET STRING } tt TT ::= {a 77,b {"kalle","kula"}}

这里定义的值可以以多种方式使用。例如,它可以用作某个DEFAULT组件的值:

SS ::= SET { s OBJECT IDENTIFIER, val TT DEFAULT tt }

它也可以在Erlang程序中使用。如果这个ASN.1代码是在ASN.1模块中定义的Values,那么ASN.1值tt可以通过函数调用从Erlang到达,'Values':tt()如下例所示:

1> Val = 'Values':tt(). {'TT',77,["kalle","kula"]} 2> {ok,Bytes} = 'Values':encode('TT',Val). {ok,<<48,18,128,1,77,161,13,4,5,107,97,108,108,101,4,4, 107,117,108,97>>} 4> 'Values':decode('TT',Bytes). {ok,{'TT',77,["kalle","kula"]}} 5>

此示例显示编译器会生成一个函数,该函数返回该值的有效Erlang表示,尽管该值为复杂类型。

此外,如果maps不使用该选项,则会为.hrl文件中的每个值生成一个宏。所以,定义的值tt也可以通过?tt应用程序代码来提取。

3.8宏

该类型MACRO不受支持。它不再是ASN.1标准的一部分。

3.9 ASN.1信息对象(X.681)

信息对象类,信息对象和信息对象集(分别在下面称为类,对象和对象集)在标准定义X.681中定义。这里只给出一个简单的解释。

这些结构可以定义开放类型,也就是说,该类型的值可以是任何ASN.1类型。而且,可以在不同类型和值之间定义关系,因为类可以在其字段中保存类型,值,对象,对象集和其他类。一个类可以在ASN.1中定义如下:

GENERAL-PROCEDURE ::= CLASS { &Message, &Reply OPTIONAL, &Error OPTIONAL, &id PrintableString UNIQUE } WITH SYNTAX { NEW MESSAGE &Message [REPLY &Reply] [ERROR &Error] ADDRESS &id }

一个对象是一个类的实例。对象集是包含指定类的对象的集合。定义可以如下所示:

object1 GENERAL-PROCEDURE ::= { NEW MESSAGE PrintableString ADDRESS "home" } object2 GENERAL-PROCEDURE ::= { NEW MESSAGE INTEGER ERROR INTEGER ADDRESS "remote" }

该对象object1是该类的一个实例,GENERAL-PROCEDURE并具有一个类型字段和一个固定类型值字段。该对象object2还有一个可选字段ERROR,它是一个类型字段。该领域ADDRESS是一个UNIQUE领域。对象集中的对象在其UNIQUE字段中必须具有唯一值,如下所示GENERAL-PROCEDURES

GENERAL-PROCEDURES GENERAL-PROCEDURE ::= { object1 | object2}

您不能对类,对象或对象集进行编码,只能在定义其他ASN.1实体时引用它。通常,您可以通过ASN.1类型中的表约束和组件关系约束(X.682)引用对象集,也可以引用对象集,如下所示:

StartMessage ::= SEQUENCE { msgId GENERAL-PROCEDURE.&id {GENERAL-PROCEDURES}), content GENERAL-PROCEDURE.&Message {GENERAL-PROCEDURES}{@msgId}), }

在类型中StartMessage,约束跟随字段content告诉在类型StartMessage值中字段中的值content必须来自由字段选择的同一对象msgId

所以,该值#'StartMessage'{msgId="home",content="Any Printable String"}作为一个StartMessage值进行编码是合法的。但是,该值#'StartMessage'{msgId="remote", content="Some String"}是非法的,因为约束条件会StartMessage告诉您,当您从GENERAL-PROCEDURES字段中的对象集中的特定对象中msgId选择一个值时,您必须从内容字段中的同一对象中选择一个值。在这第二种情况下,它是任何INTEGER价值。

StartMessage可以在字段content中用对象集中的对象GENERAL-PROCEDURES在其NEW MESSAGE字段中具有的任何类型的值进行编码。该字段指的是类&Message中的类型字段。字段msgId始终编​​码为a PrintableString,因为该字段指的是类中的固定类型。

实际上,对象集通常被声明为可扩展的,以便稍后可以将更多的对象添加到集合中。可扩展性如下所示:

GENERAL-PROCEDURES GENERAL-PROCEDURE ::= { object1 | object2, ...}

当解码使用可扩展集合约束的类型时,字段中的值始终可能UNIQUE是未知的(也就是说,该类型已使用ASN.1规范的更高版本进行编码)。然后将未编码的数据返回包装在一个元组中,如下所示:

{asn1_OPENTYPE,Binary}

Binary是一个包含编码数据的Erlang二进制文件。(如果legacy_erlang_types已经给出选项,则只返回二进制文件。)

3.10参数化(X.683)

定义类型,值,值集,类,对象或对象集时,可以使用X.683中定义的参数化。定义的一部分可以作为参数提供。例如,如果Type在具有特定目的的定义中使用a,则希望类型名称表达意图。这可以通过参数化来完成。

当许多类型(或另一个ASN.1实体)在一些较小的情况下仅有不同时,但类型的结构相似时,只能定义一个通用类型,并且可通过参数提供差异。

参数化使用示例:

General{Type} ::= SEQUENCE { number INTEGER, string Type } T1 ::= General{PrintableString} T2 ::= General{BIT STRING}

一个可以编码为type的值的例子T1{12,"hello"}

请注意,编译器不会为参数化类型生成编码/解码函数,仅针对参数化类型的实例。因此,如果文件包含类型General{}T1以及T2如先前实例中,编码/解码功能仅用于产生T1T2