查看源代码 入门指南
示例
以下示例演示了运行 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
可以添加选项 verbose
以获取有关生成文件的信息
2> asn1ct:compile("People", [ber,verbose]).
Erlang ASN.1 compiling "People.asn"
--{generated,"People.asn1db"}--
--{generated,"People.hrl"}--
--{generated,"People.erl"}--
ok
现在接受了 ASN.1 模块 People
,并且抽象语法树保存在文件 People.asn1db
中。生成的 Erlang 代码使用 Erlang 编译器进行编译并加载到 Erlang 运行时系统中。现在在模块 People
中有一个 encode/2
和 decode/2
的 API,调用方式如下
'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 类型 Person
的 Erlang 值。然后,构建答案并使用 'People':encode('Person',Answer)
进行编码,该调用接受定义的 ASN.1 类型的实例,并根据 BER 或 PER 编码规则将其转换为二进制。
编码器和解码器也可以从 shell 运行
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}}
模块依赖
ASN.1 模块从另一个 ASN.1 模块导入定义的类型、值和其他实体是很常见的。
早期版本的 ASN.1 编译器要求导入的模块必须在导入的模块之前进行编译。当 ASN.1 模块具有循环依赖关系时,这会导致问题。
现在,当编译器找到导入的实体时,会解析引用的模块。不会为引用的模块生成代码。但是,编译后的模块依赖于引用的模块也被编译。
ASN.1 应用程序用户界面
ASN.1
应用程序提供了以下两个独立的用户界面
- 模块
asn1ct
,提供编译时函数(包括编译器) - 模块
asn1rt_nif
,为 BER 后端的 ASN.1 解码器提供运行时函数
将接口划分为编译时和运行时的原因是,只有运行时模块(asn1rt_nif
)才需要在嵌入式系统中加载。
编译时函数
可以通过 erlc
程序直接从命令行启动 ASN.1 编译器。当从命令行编译多个 ASN.1 文件或使用 Makefiles 时,这很方便。以下是一些示例,显示了 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 | jer]
- 编码规则的选择。如果省略,则默认为ber
。-o OutDirectory
- 将生成的文件放置在哪里。默认为当前目录。-I IncludeDir
- 在哪里搜索.asn1db
文件和 ASN.1 源规范,以解析对其他模块的引用。如果有多个搜索位置,则可以多次重复此选项。编译器首先搜索当前目录。+der
- DER 编码规则。仅当使用选项-bber
时。+jer
- JSON 编码规则的函数jer_encode/2
和jer_decode/2
与ber
或per
的函数一起生成。仅当主要编码选项为-bber
、-bper
或-buper
时使用。+maps
- 使用映射而不是记录来表示SEQUENCE
和SET
类型。不会生成.hrl
文件。有关详细信息,请参阅SEQUENCE 和 SET 的映射表示部分。+asn1config
- 此功能与选项ber
一起使用。它启用了专门的解码,请参阅 专门解码部分。+undec_rest
- 保存正在解码的消息的缓冲区也可能具有尾随字节。如果这些尾随字节很重要,则可以通过使用选项+undec_rest
编译 ASN.1 规范,将它们与解码后的值一起返回。解码器的返回值是{ok,Value,Rest}
,其中Rest
是包含尾随字节的二进制文件。+'Any Erlc Option'
- 可以在编译生成的 Erlang 文件时将任何选项添加到 Erlang 编译器。ASN.1 编译器无法识别的任何选项都会传递给 Erlang 编译器。
有关 erlc
的完整描述,请参阅 ERTS 参考手册。
编译器和其他编译时函数也可以从 Erlang shell 启动。以下是对主要函数的简要说明。有关每个函数的完整描述,请参阅ASN.1 参考手册中的模块 asn1ct
。
编译器通过带有默认选项的asn1ct:compile/1
或在给出显式选项时通过 asn1ct:compile/2
启动。
示例
asn1ct:compile("H323-MESSAGES").
这等效于
asn1ct:compile("H323-MESSAGES", [ber]).
如果需要 PER 编码
asn1ct:compile("H323-MESSAGES", [per]).
可以按如下方式调用通用编码和解码函数
'H323-MESSAGES':encode('SomeChoiceType', {call,<<"octetstring">>}).
'H323-MESSAGES':decode('SomeChoiceType', Bytes).
运行时函数
当使用选项 ber
编译 ASN.1 规范时,运行时需要 asn1rt_nif
模块和 asn1/priv_dir
中的 NIF 库。
通过在生成的模块中调用函数 info/0
,您可以获得有关使用了哪些编译器选项的信息。
错误
在编译时检测到的错误会显示在屏幕上,并带有行号,指示在源文件中检测到相应错误的位置。如果未找到错误,则会创建一个 Erlang ASN.1 模块。
运行时编码器和解码器在 catch
中执行,并返回 {ok, Data}
或 {error, {asn1, Description}}
,其中 Description
是描述错误的 Erlang 项。
目前,Description
如下所示:{ErrorDescription, StackTrace}
。应用程序不应依赖于 Description
的确切内容,因为它将来可能会更改。
多文件编译
使用多文件编译的原因有很多
- 例如,要为生成的模块选择名称,因为您需要为不同的编码规则编译相同的规范。
- 您只需要一个生成的模块。
指定要在扩展名为 .set.asn
的模块中编译的 ASN.1 规范。选择一个模块名称并提供 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
关于标签的说明
标签过去对所有 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
ASN.1 类型
本节描述 ASN.1 类型,包括其功能、用途以及如何在 Erlang 中赋值。
ASN.1 具有原始类型和构造类型
原始类型 | 构造类型 |
---|---|
BOOLEAN | SEQUENCE |
INTEGER | SET |
REAL | CHOICE |
NULL | SET OF 和 SEQUENCE OF |
ENUMERATED | ANY |
BIT STRING | ANY DEFINED BY |
OCTET STRING | EXTERNAL |
字符串 | EMBEDDED PDV |
OBJECT IDENTIFIER | 字符串 |
对象描述符 | |
时间类型 |
表格:支持的 ASN.1 类型
注意
每个 ASN.1 类型的值在 Erlang 中都有自己的表示形式,如下节所述。用户必须根据表示形式提供这些值进行编码,如下例所示
Operational ::= BOOLEAN --ASN.1 definition
在 Erlang 代码中,它可能如下所示
Val = true,
{ok,Bytes} = MyModule:encode('Operational', Val),
BOOLEAN
ASN.1 中的布尔值表示可以是 TRUE
或 FALSE
的值。TRUE
和 FALSE
的含义不在本文的讨论范围之内。
在 ASN.1 中,可以有
Operational ::= BOOLEAN
在 Erlang 中,可以使用以下 Erlang 代码为 Operational
类型赋值
Myvar1 = true,
因此,在 Erlang 中,原子 true
和 false
用于编码布尔值。
INTEGER
ASN.1 INTEGER 由 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)}
如果指定了 Named Number List
(请参阅上一个列表中的 T6
),则 ASN.1 INTEGER
的 Erlang 表示形式为整数或原子。
以下是 Erlang 代码的示例,该代码为上一个列表中的类型赋值
T1value = 0,
T2value = 6,
T6value1 = blue,
T6value2 = 0,
T6value3 = white
这些 Erlang 变量现在绑定到 ASN.1 定义类型的有效实例。这种类型的值可以直接传递给编码器,以转换为一系列字节。
如果值与 Named Number List
中的符号相对应,则解码器返回一个原子。
REAL
以下 ASN.1 类型用于表示实数
R1 ::= REAL
它在 Erlang 中赋值如下
R1value1 = "2.14",
R1value2 = {256,10,-2},
在最后一行中,请注意,元组 {256,10,-2} 是以特殊表示法表示的实数 2.56,其编码速度比简单地将数字表示为 "2.56"
快。三元组的含义是 {尾数,底数,指数}
,即 尾数 * 底数^指数。
NULL
类型 NULL
适用于供应和识别值很重要,但实际值并不重要的情况。
Notype ::= NULL
此类型在 Erlang 中赋值如下
N1 = 'NULL',
实际值是带引号的原子 'NULL'
。
ENUMERATED
当要描述的值只能取一组预定义值中的一个时,可以使用类型 ENUMERATED
。示例
DaysOfTheWeek ::= ENUMERATED {
sunday(1),monday(2),tuesday(3),
wednesday(4),thursday(5),friday(6),saturday(7) }
例如,要在 Erlang 中赋值一个工作日值,请使用与类型定义的 Enumerations
中相同的原子
Day1 = saturday,
枚举类型与使用一组预定义值定义的整数类型类似。区别在于枚举类型只能具有指定的值,而整数可以具有任何值。
BIT STRING
类型 BIT STRING
可用于对由任意长度的位序列组成的信息进行建模。它旨在用于选择标志,而不是用于二进制文件。
在 ASN.1 中,BIT STRING
定义可以如下所示
Bits1 ::= BIT STRING
Bits2 ::= BIT STRING {foo(0),bar(1),gnu(2),gnome(3),punk(14)}
以下两种表示法可用于表示 Erlang 中的 BIT STRING
值并作为编码函数的输入
- 一个位串。默认情况下,没有符号名称的
BIT STRING
将解码为 Erlang 位串。 - 与
BIT STRING
定义中的NamedBitList
中的原子相对应的原子列表。具有符号名称的BIT STRING
始终解码为以下示例中显示的格式
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 的任何位。
BIT STRING 的弃用表示形式
除了前面描述的表示形式外,如果使用选项 legacy_erlang_types
编译了规范,则可以使用以下弃用的表示形式
- 作为二进制数字(0 或 1)的列表。此格式被接受为编码函数的输入,如果给定选项
legacy_bit_string
,则BIT STRING
将解码为此格式。 - 作为
{Unused,Binary}
,其中Unused
表示在Binary
中最低有效字节中有多少个未使用的尾随零位 0-7。此格式被接受为编码函数的输入,如果已给定compact_bit_string
,则BIT STRING
将解码为此格式。 - 作为十六进制数(或整数)。避免使用此方法,因为很容易误解此格式的
BIT STRING
值。
OCTET 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 编译器不确定使用不同字符串的每个 BER 字符串八位字节值的正确解释。应用程序负责解释八位字节。因此,从 BER 字符串的角度来看,八位字节与字符串非常相似,并且以相同的方式编译。
使用 PER 时,OCTET STRING
和其他字符串的编码方案存在显着差异。为类型指定的约束对于 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:characters_to_binary/1
从 Unicode 代码点列表转换而来。
以下显示了如何创建和操作 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]
有关详细信息,请参阅 STDLIB 中的 unicode
模块。
在以下示例中,使用了此 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
只要需要唯一标识,就会使用类型 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 要求被认为是应用程序需要满足的。
SEQUENCE
ASN.1 的结构化类型由其他类型构造而成,类似于 C 语言中的数组和结构体的概念。
ASN.1 中的 SEQUENCE
类似于 C 语言中的结构体和 Erlang 中的记录。SEQUENCE
可以定义如下:
Pdu ::= SEQUENCE {
a INTEGER,
b REAL,
c OBJECT IDENTIFIER,
d NULL }
这是一个名为 Pdu
的 4 组件结构。默认情况下,SEQUENCE
在 Erlang 中用记录表示。它也可以用映射表示;请参阅 SEQUENCE 和 SET 的映射表示。对于 ASN.1 模块中的每个 SEQUENCE
和 SET
,都会生成一个 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'}.
当解码 SEQUENCE
或 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 编码规则,则解码 SET
比解码 SEQUENCE
慢,因为必须对组件进行排序。
SEQUENCE 和 SET 的可扩展性
当 SEQUENCE
或 SET
包含扩展标记和扩展组件(如下所示)时,该类型可以在较新版本的 ASN.1 规范中获得更多组件:
SExt ::= SEQUENCE {
a INTEGER,
...,
b BOOLEAN }
在这种情况下,它获得了一个新组件 b
。因此,解码的传入消息可能具有比此消息更多或更少的组件。
组件 b
在编码消息时被视为原始组件。在这种情况下,由于它不是可选元素,因此必须对其进行编码。
在解码期间,如果存在 b
组件,则记录的 b
字段会获得解码值,否则该值将为 asn1_NOVALUE
。
SEQUENCE 和 SET 的映射表示
如果 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
类型 CHOICE
是一种节省空间的方法,类似于 C 语言中的联合概念。
假设以下情况:
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
包含扩展标记并且解码器检测到 CHOICE
的未知备选项时,该值表示如下:
{asn1_ExtAlt, BytesForOpenType}
此处 BytesForOpenType
是构成“未知”CHOICE
备选项编码的字节列表。
SET OF 和 SEQUENCE OF
类型 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
类型 ANY
和 ANY DEFINED BY
自 1994 年以来已从标准中删除。建议不要再使用这些类型。但是,它们可能存在于一些旧的 ASN.1 模块中。此类型的目的是在定义中留下一个“漏洞”,可以在其中放置任何类型的未指定数据,甚至是非 ASN.1 数据。
此类型的值被编码为 open type
。
建议使用信息对象类、表约束和参数化,而不是 ANY
和 ANY DEFINED BY
。特别是,构造 TYPE-IDENTIFIER.@Type
完成了与已弃用的 ANY
相同的功能。
另请参阅信息对象。
EXTERNAL、EMBEDDED PDV 和 CHARACTER STRING
类型 EXTERNAL
、EMBEDDED PDV
和 CHARACTER STRING
用于表示层协商。它们根据其关联的类型进行编码,请参阅 X.680。
在 1994 年之前,类型 EXTERNAL
具有略有不同的关联类型。X.691 声明编码必须遵循较旧的关联类型。因此,生成的编码/解码函数会在编码之前将较新格式的值转换为较旧格式。这意味着允许使用任何格式的 EXTERNAL
类型值进行编码。解码后的值始终以较新的格式返回。
嵌入式命名类型
先前描述的结构化类型可以具有其他命名类型作为其组件。在 Erlang 中,将值分配给命名 ASN.1 类型 T
的组件 C
的通用语法是记录语法 #'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>>}
.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}).
参数化类型被视为嵌入式类型。每次引用此类类型时,都会定义其一个实例。因此,在以下示例中,会在 .hrl
文件中生成一个名为 'Seq_b'
的记录,并用于保存值:
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 到 Erlang 编译器支持此递归类型。此类型的值在 Erlang 中按如下方式赋值:
V = {something,#'Rec_something'{a = 77,
b = "some octets here",
c = {nothing,'NULL'}}}.
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
中定义,则可以从 Erlang 中通过调用函数 'Values':tt()
来访问 ASN.1 值 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
提取。
宏
不支持 MACRO
类型。它不再是 ASN.1 标准的一部分。
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
中的约束表明,当您在字段 msgId
中从对象集 GENERAL-PROCEDURES
中的特定对象中选择一个值时,您还必须从内容字段中的同一对象中选择一个值。在第二种情况下,它应该是任何 INTEGER
值。
StartMessage
可以在字段 content
中编码对象集 GENERAL-PROCEDURES
中的对象在其 NEW MESSAGE
字段中具有的任何类型的值。此字段引用类中的类型字段 &Message
。字段 msgId
始终编码为 PrintableString
,因为该字段引用类中的固定类型。
在实践中,对象集通常被声明为可扩展的,以便以后可以向该集合添加更多对象。可扩展性表示如下:
GENERAL-PROCEDURES GENERAL-PROCEDURE ::= {
object1 | object2, ...}
当解码使用可扩展集约束的类型时,字段 UNIQUE
中的值总是可能是未知的(也就是说,该类型是用更高版本的 ASN.1 规范编码的)。然后,未编码的数据将包装在一个元组中返回,如下所示:
{asn1_OPENTYPE,Binary}
这里 Binary
是一个包含编码数据的 Erlang 二进制文件。(如果给定了选项 legacy_erlang_types
,则只返回二进制文件。)
参数化 (X.683)
在定义类型、值、值集、类、对象或对象集时,可以使用 X.683 中定义的参数化。定义的一部分可以作为参数提供。例如,如果在某个定义中使用 Type
并具有特定用途,则希望类型名称表达该意图。这可以通过参数化来实现。
当许多类型(或其他 ASN.1 实体)仅在某些细微之处有所不同,但类型的结构相似时,可以只定义一个通用类型,并且可以通过参数提供差异。
参数化使用示例:
General{Type} ::= SEQUENCE
{
number INTEGER,
string Type
}
T1 ::= General{PrintableString}
T2 ::= General{BIT STRING}
可以编码为类型 T1
的一个值的示例是 {12,"hello"}
。
请注意,编译器不会为参数化类型生成编码/解码函数,只会为参数化类型的实例生成。因此,如果文件包含如上例所示的类型 General{}
、T1
和 T2
,则只会为 T1
和 T2
生成编码/解码函数。