深入理解Net-Snmp
上QQ阅读APP看书,第一时间看更新

2.5 数据类型

在ASN.1中定义了多种数据类型,它们属于通用类,也是ASN.1中的内置数据类型,是在各种应用场合中都可以直接使用的数据类型。由这些基本的数据类型可以定义更多的数据类型结构。

这些数据类型与具体的编程语言中定义的数据类型相似,有整型、字符型、枚举型、结构(体)型。当然这些数据类型不涉及物理的存储方式,而是一种“抽象的”数据类型。

表2-5中左侧的“Tag”字段中UNIVERSAL,表明该数据类型属于UNIVERSAL类,后面紧接的数值是该数据类型所对应的标签号,也就是编码,这些编码在信息编码和传输时使用。

表2-5 ASN.1通用数据类型

在ASN.1中通用类的数据类型里又分为两大类:简单数据类型(PRIMITIVE);构造数据类型(CONSTRUCTED),简称为P/C。具体内容如下文所述。

2.5.1 简单数据类型

简单数据类型主要包括表2-5中的数值型、字符串型、布尔型、标识符型等。下面列举了MIB中常用的数据类型,建议按照以下方式定义和使用(实际上SNMP中对一些简单的数据类型进行了重定义,其体现在SMI中)。

·BOOLEAN:使用BOOLEAN定义类型别名时,将该类型别名取为表示“true”状态的名称。如:

Married ::= BOOLEAN

而不建议使用:

MaritalStatus ::= BOOLEAN

·INTEGER:定义包含最大和最小边界时,注意以下的方法——限制只取两个值first和last。

DayOfWeeks ::= INTEGER {first(1), last(7)} (first | last)

限制取值范围为first和last区间内的值:

DayOfWeeks ::= INTEGER {first(1), last(7)} (first .. last)
dayOfWeeks DayOfWeeks ::= 2

·ENUMERATED:在定义不少于两个状态时,或现在是两种状态而将来可能有多于两种状态的情况时,建议使用枚举型。

--以枚举型定义星期数据类型
DayOfWeeks2 ::= ENUMERATED {sunday(0), monday(1), tuesday(2),wednesday(3),
thursday(4), friday(5), saturday(6)}

需要使用值标识符表示具体的值内容,不使用数值相等的数字,其枚举之外的数值理所当然地不能使用,如:

-- 使用值标识符 sunday 表示具体的值
firstDay DayOfTheWeek ::= sunday

而不应该使用:

firstDay DayOfTheWeek ::= 0
whichDay DayOfTheWeek ::= 8

便于后续扩展的写法是加入扩展符“…”:

-- 第一版
MaritalStatus ::= ENUMERATED {single, married}
-- 后续(升级)版本
MaritalStatus ::= ENUMERATED {single, married, , widowed}
MaritalStatus ::= ENUMERATED {single, married, , widowed, divorced}

·Bitstring:位类型,其字串格式和长度不作规定,可以使用不同进制表示。

hexvalue BIT STRING ::= '0123456789ABCDEF'H
bitvalue BIT STRING ::= '1001'B

当然对该类型也可以做长度限制,使用限制后的类型定义时其位数必须为限定的长度:

MyBitField ::= BIT STRING (SIZE (10))
m1 BitField ::= '1010001000'B
m2 BitField ::= '288'H --same as m1

由于bit类型的特点,可以使用BIT STRING定义类似枚举型的数据。

DayOfWeeks3 ::= BitString {monday (0), tuesday (2), wednesday (3), thursday (4), 
friday (5), saturday (6), sunday (7)}

·Octet string:与Bitstring类型的区别是,使用Octet string定义的数据长度要求为8的整数倍,这也是octet单词含义的体现——八位比特组。一般所使用的ASCII字符,都是能满足要求的。在SNMP文献RFC1213、MIB-II中定义的DisplayString就是该类型。

DisplayString ::=  OCTET STRING
-- 使用
whoyouare DisplayString ::= "Chanson"

·CharacterString:字符串类型,如UniversalString、PrintableString、NumericString、IA5String、BMPString、UTF8String等。它们的主要区别是使用的字符集标准不一样。

·Null:代表空类型,用于表示在定义的序列中可以为空的部分。如SNMP中Get请求报文中的值部分为空值,表示数据未知。

·Object identifier:对象标识符OBJECE IDENTIFIER,这两个单词都是ASN.1中的保留字。对象标识符在SNMP中是最重要的数据类型之一,为此下面给出了其类型定义和值定义的BNF形式。

类型定义:

ObjectIdentifierType ::=OBJECT IDENTIFIER

值定义:

ObjectIdentifierValue ::=
        "{" ObjIdComponentsList "}"
        | "{" DefinedValue ObjIdComponentsList "}"
ObjIdComponentsList ::=
        ObjIdComponents
        | ObjIdComponents ObjIdComponentsList
ObjIdComponents ::=
        NameForm
        | NumberForm
        | NameAndNumberForm
        | DefinedValue
NameForm ::= identifier
NumberForm ::= number | DefinedValue
NameAndNumberForm ::=
        identifier "(" NumberForm ")"

让我们再来回顾下,上述的值定义解开后的内容是怎样的。首先,发现了无法确定含义的单词“DefinedValue”,也有表2-4中的基本词汇“identifier”“number”,它们都是作为产生式的终结符。只有明确了这些终结符,才能产生最终的内容。其次,还发现“DefinedValue”出现在多个产生式中,很明显其含义是不一样的。实际上它被定义在文献《ITU-T Rec.X.680》中。在这里的每个产生式中分别为对应类型的值引用。“identifier”则是具体的对象标识符和值的名称,为具体的字符串;“number”为非负的整数;引号中作为终结符的大括号直接照写。将这些终结符不断地向上层替换,就能得到ObjectIdentifierValue的最终结果:由具体的字符串、具体的数字或它们的混合方式组成,最终由大括号包含。它们可以是以下的形式:

{ iso standard 8571 application-context (1) }
{ 1 0 8571 1 }
-- 实际应用中也可以表示如下
iso.standard.8571.application-context(1)
1.0.8571.1

其中iso为值标识符,其对应的值为1,其他符号与值的含义类似。当然这些标识符和值,是由某些组织或企业申请和定义的,具有唯一性和明确含义的。

·日期和时间类型:最常见的是以UTC(Coordinated Universal Time,协调世界时,UTC三个字母缩写为英文和法文折中的记法)记法表示的时间。该记法的方式是在时间字串后紧跟一个大写的字母Z,或者在小时和分钟后紧跟“+”或“-”号,表示与UTC时间的差。比如下面的ASN.1允许的时间记法:

-- 当前UTC时间:2014-03-06 01:20:00
140306012000Z
-- 对应的北京时间为加八个时区(即8个小时),记法为:
140306092000+0800

2.5.2 构造数据类型

构造数据类型指的是由简单数据类型或构造类型组成的。一个不包含构造类型的构造类型一定是由简单数据类型组成的。ASN.1中主要有下面的构造数据类型:列表(SEQUENCE)、列表结构(SEQUENCE OF)、集合(SET)、集合结构(SET OF)、类型选择(CHOICE)。在SNMP中使用SEQUENCE较多。下面对其进行简要的介绍。

·SEQUENCE:表示各类型的有序组成,如定义姓名时,以先姓后名的顺序表示;传输时按定义的先后顺序一一传输。这可以类比于C语言中的struct类型。一般和SEQUENCE OF配合使用。

·SEQUENCE OF:由SEQUENCE元素组成的列表类型。从SNMP实际应用情况来理解,它定义的是一个表格(多维),而表格中的一行是由SEQUENCE定义的,与SEQUENCE相同的是Universal Tag。

·SET:与SEQUENCE类似,不过两者根本的区别是有序还是无序。SET表示各类型是无序组成的集合,在SNMP中没有使用。

·SET OF:与SEQUENCE OF类似,不过是无序集合,在SNMP中没有使用。

2.5.3 其他类型

另外,较为常见的两种类型是CHOICE和ANY,实际上它们不是真正的数据类型,而是一种定义方法。

·CHOICE:字面上理解为选择类型,指的是在一组类型中选择其中某一项数据类型。当备选类型中有多个相同的数据类型,需要显式地指定子Tag以作区分。由于CHOICE不是具体的数据类型,表2-5中没有对应的Tag字段。为了能正确地进行编码传输,其Tag由被选中数据类型的Tag表示。CHOICE一般用于事先已经知道有哪些数据类型可供选择的情况。

--可选择的支付方式:信用卡或现金
PaymentMethod ::= CHOICE {
  credit-card   NumericString,
  cash       INTEGER
}

·ANY:可选择ASN.1中的任意类型,相比CHOICE来说,ANY可选择的类型更广,甚至是将来可能出现的类型。

2.5.4 子类型

ASN.1还基于上述的数据类型定义了相关的约束。基于这些约束可定义意义更为明确的对象,这种情况主要应用于具体的场景。比如2.5.1节中星期枚举型的定义,其取值或者取值范围是被限定的。ASN.1中的约束说明比较完善,有单独的文献。有单值约束、值域约束、大小约束,也有类型、模式、属性、时间点等约束。下面简要地示例说明单值、值域、大小约束,因为它们是实际使用中最为常见的约束类型,所使用的符号前文已有定义。

·单值约束:将某个类型约束为一个值。定义方法为圆括号中包含被限制的值。如:

OnlyoneValue ::= INTEGER (1 | 2 | 3 | 6)

·值域约束:使用区间符号表示值域约束。默认包含区间边界,如果不包含边界则使用大于和小于符号。如:

From2to6 ::= INTEGER (1<..<7)

·大小约束:大小约束与值域约束稍微有些差异,指的是对空间大小的限制。如:

--使用String32Len定义的变量其值的字符长度为32
String32Len ::= BIT STRING (SIZE (32))

·字符表约束:指的是只取部分字符,使用FROM保留字定义。如:

BooleanValue ::= IA5String (FROM ('T' | 'F'))

·包含子类型:将某种子类型包含进来作为其中的一部分。如:

ContainedType ::=INTEGER(INCLUDES OnlyoneValue |  7  | 8)

子类型的Tag与被约束类型的Tag相同。

2.5.5 标签类型定义

在2.5.3节中,给出了一个CHOICE的例子,也提到了“显式地指定子Tag以作区分”。下面我们再看看另外一个例子,并考虑如何将这个例子中的数据编码进行传输。

--定义体重和身高
WhtHht ::= CHOICE {
        weight  INTENGER, --体重
        height  INTENGER } -身高

当我们以INTEGER数据类型标签传送数据的时候,会出现什么问题?无法区分传送的是“体重”还是“身高”,这就需要使用另外的定义机制。

标签类型的定义指的是,通过定义标签区分构造类型中具有相同数据类型(简单或构造类)的不同组件。定义方法有两种:显式(EXPLICIT)和隐式(IMPLICIT),其使用方法如下:

< 新类型名 > ::= [ [标签类名] 标记号    ] [ EXPLICIT | IMPLICIT ]  < 原始类型 >

上式中,“标签类名”指的是2.4节中的几种标签类,默认为context-specific即上下文指定类(请理解上下文的含义);“显式|隐式”也为可选项,那是因为默认为IMPLICIT;“原始类型”可以为简单数据类型或构造类型。无论是以显式还是隐式定义新的标签类型,都能够区分构造体中的数据,它们的主要区别在于编码方式上的差异(编码在第5章讲述)。

我们把上面的例子重定义如下(默认的定义方式:上下文指定类,隐式类型):

--定义体重和身高
WhtHht ::= CHOICE {
        weight [1] INTENGER, --体重,通过标记号 1 唯一确定
        height [2] INTENGER  -身高,通过标记号 2 唯一确定
}