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/2
和decode/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
使用地图而不是记录来表示SEQUENCE
和SET
类型。.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启动。下面简要介绍主要功能。对于每个功能的完整描述,请参阅模块asn1ct
中ASN.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规范与选项编译ber
的asn1rt_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.asn
,File2.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 Types | Constructed Types |
---|---|
BOOLEAN | SEQUENCE |
INTEGER | SET |
REAL | CHOICE |
NULL | SET OF and SEQUENCE OF |
ENUMERATED | ANY |
BIT STRING | ANY DEFINED BY |
OCTET STRING | EXTERNAL |
Character Strings | EMBEDDED PDV |
OBJECT IDENTIFIER | CHARACTER 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
。分配给的含义TRUE
和FALSE
在本文的范围之外。
在ASN.1中,有可能有:
Operational ::= BOOLEAN
Operational
通过使用以下Erlang代码,可以指定一个值来输入Erlang:
Myvar1 = true,
因此,在Erlang原子true
和false
用于编码一个布尔值。
整型
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 STRING
Erlang中的值以及作为编码函数的输入:
- 比特串。默认情况下,
BIT STRING
没有符号名称的解码为Erlang位串。
NamedBitList
与BIT 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],
Bits2Val2
和Bits2Val3
表示相同的值。
Bits2Val1
被分配符号值。分配意味着对应的位gnu
和punk
,也就是第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 STRING
s和其他字符串之间的编码方案存在显着差异。为类型指定的约束对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]
有关详细信息,请参阅unicode
STDLIB中的模块。
在下面的例子中,使用了这个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中的数组和结构的概念的方式从其他类型构造而来的。
SEQUENCE
ASN.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
。对于每个SEQUENCE
和SET
一个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
。
SEQUENCE
和SET
可以包含具有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
,类型SEQUENCE
和SET
被表示为地图。
在以下示例中,使用了本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_VALUE
和asn1_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 OF
与SEQUENCE 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
如果可能的话。
由
类型ANY
和ANY DEFINED BY
已经从自1994年以来,建议不要再使用这些类型的标准中删除。但是,它们可以存在于一些旧的ASN.1模块中。这种类型的想法是在一个定义中留下一个“漏洞”,在这个定义中可以放置任何类型的非特定数据,即使是非ASN.1数据。
这种类型的值被编码为open type
。
取而代之的ANY
和ANY DEFINED BY
,建议使用information object class
,table constraints
和parameterization
。特别是该构造TYPE-IDENTIFIER.@Type
完成与已弃用的相同ANY
。
另见Information object
。
外部、嵌入式PDV和字符串
表示层协商中使用的类型EXTERNAL
,EMBEDDED PDV
和CHARACTER 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
SEQUENCE
b
可以用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
文件中产生相应的记录。这是因为价值观SET
和SEQUENCE
表示为默认的记录。
下一节将介绍此功能的一些特殊情况。
嵌入式结构化类型
在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
/ SET
在SEQUENCE
/中SET
,记录名称使用下划线和组件名称进行扩展。如果嵌入式结构与更深SEQUENCE
,SET
或CHOICE
类型的线,每个部件名称/替代名称被添加到记录名称。
例子:
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
如先前实例中,编码/解码功能仅用于产生T1
和T2
。