查看源代码 入门指南

示例

以下示例演示了运行 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/2decode/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/2jer_decode/2berper 的函数一起生成。仅当主要编码选项为 -bber-bper-buper 时使用。

  • +maps - 使用映射而不是记录来表示 SEQUENCESET 类型。不会生成 .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.asnFile2.asnFile3.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 具有原始类型和构造类型

原始类型构造类型
BOOLEANSEQUENCE
INTEGERSET
REALCHOICE
NULLSET OF 和 SEQUENCE OF
ENUMERATEDANY
BIT STRINGANY DEFINED BY
OCTET STRINGEXTERNAL
字符串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 中的布尔值表示可以是 TRUEFALSE 的值。TRUEFALSE 的含义不在本文的讨论范围之内。

在 ASN.1 中,可以有

Operational ::= BOOLEAN

在 Erlang 中,可以使用以下 Erlang 代码为 Operational 类型赋值

Myvar1 = true,

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

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 值并作为编码函数的输入

  1. 一个位串。默认情况下,没有符号名称的 BIT STRING 将解码为 Erlang 位串。
  2. 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],

Bits2Val2Bits2Val3 表示相同的值。

Bits2Val1 被赋值为符号值。赋值表示与 gnupunk 对应的位,即第 2 位和第 14 位设置为 1,其余位设置为 0。符号值显示为值列表。如果显示了类型定义中未指定的命名值,则会发生运行时错误。

BIT STRING 也可以使用例如 SIZE 规范进行子类型化

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

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

BIT STRING 的弃用表示形式

除了前面描述的表示形式外,如果使用选项 legacy_erlang_types 编译了规范,则可以使用以下弃用的表示形式

  1. 作为二进制数字(0 或 1)的列表。此格式被接受为编码函数的输入,如果给定选项 legacy_bit_string,则 BIT STRING 将解码为此格式。
  2. 作为 {Unused,Binary},其中 Unused 表示在 Binary 中最低有效字节中有多少个未使用的尾随零位 0-7。此格式被接受为编码函数的输入,如果已给定 compact_bit_string,则 BIT STRING 将解码为此格式。
  3. 作为十六进制数(或整数)。避免使用此方法,因为很容易误解此格式的 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 ....]

BMPStringUniversalString 的 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 模块中的每个 SEQUENCESET,都会生成一个 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'}.

当解码 SEQUENCESET 时,解码函数返回一个记录作为结果。

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 编码规则,则解码 SET 比解码 SEQUENCE 慢,因为必须对组件进行排序。

SEQUENCE 和 SET 的可扩展性

SEQUENCESET 包含扩展标记和扩展组件(如下所示)时,该类型可以在较新版本的 ASN.1 规范中获得更多组件:

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

在这种情况下,它获得了一个新组件 b。因此,解码的传入消息可能具有比此消息更多或更少的组件。

组件 b 在编码消息时被视为原始组件。在这种情况下,由于它不是可选元素,因此必须对其进行编码。

在解码期间,如果存在 b 组件,则记录的 b 字段会获得解码值,否则该值将为 asn1_NOVALUE

SEQUENCE 和 SET 的映射表示

如果 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

类型 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 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 OFSEQUENCE OF 之间没有区别。Erlang 的 ASN.1 编译器不会在编码之前随机化 SET OF 组件的顺序。

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

ANY 和 ANY DEFINED BY

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

此类型的值被编码为 open type

建议使用信息对象类、表约束和参数化,而不是 ANYANY DEFINED BY。特别是,构造 TYPE-IDENTIFIER.@Type 完成了与已弃用的 ANY 相同的功能。

另请参阅信息对象

EXTERNAL、EMBEDDED PDV 和 CHARACTER STRING

类型 EXTERNALEMBEDDED PDVCHARACTER 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 规范时,类型为 SETSEQUENCE 的所有定义的类型都会在生成的 .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/SET 中的 SEQUENCE/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}).

参数化类型被视为嵌入式类型。每次引用此类类型时,都会定义其一个实例。因此,在以下示例中,会在 .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{}T1T2,则只会为 T1T2 生成编码/解码函数。