在介绍本文的主角asn1c之前,我们这里先看一下维基百科对ASN.1标准的描述:
在电信和计算机网络领域,ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法。它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。
简单来说,ASN.1定义了一套描述数据的描述方法,这种方法与语言无关,无论是C语言、Java还是Go,都能通过语言本身的语法来进行定义、描述数据。在通信行业中,经常使用ASN.1来定义数据。
我们这里要介绍的asn1c是一个基于ASN.1语法的编译器。能根据给定的ASN.1描述文件来生成对应的C语言代码,包含了BER/OER/PER/XER多种格式的编解码C语言接口,有助于加快嵌入式设备对ASN.1数据的理解和处理。
开源ASN.1编译器介绍
ANS1C的全称是ASN.1 Compiler,主要用来根据ASN.1文件来生成对应的兼容C++的C代码。支持BER/OER/PER/XER多种格式的编解码。
ASN1C是一个编译器,以ASN.1语法和ASN.1文件生成语法树,然后编译出目标语言,我们这里主要用C语言,以后要学习编译器,相信研究一下asn1c也是个不错的选择,这里我们先了解它的使用。
- 作者:Lev Walkin vlm@lionet.info
- ASN.1版本:0.9.29
- 开源地址:asn1c
安装
以下演示在Ubuntu20.04虚拟机上,通过源码安装asn1c编译器,其他平台过程类似。
环境依赖
- automake大于1.15
- libtool
- bison等于2.x,实测3.5.1版本也可以使用
- flex
从源码编译
执行如下指令,下载源码到本地,然后进入asn1c文件夹;
git clone git@github.com:vlm/asn1c.git cd asn1c
使用默认配置设置工程:
test -f configure || autoreconf -iv ./configure make
配置成功后效果如下:
╭─ caeri@ubuntu ~/lzp/GIT/asn1c ╰─ test -f configure || autoreconf -iv utoreconf: Entering directory `.' ....... onfigure.ac:5: installing 'config/install-sh' configure.ac:5: installing 'config/missing' parallel-tests: installing 'config/test-driver' asn1-tools/enber/Makefile.am: installing 'config/depcomp' autoreconf: running: gnu-configize autoreconf: Leaving directory `.'
╭─ caeri@ubuntu ~/lzp/GIT/asn1c ╰─ ./configure checking for a BSD-compatible install... /usr/bin/install -c ...... config.status: creating config.h config.status: executing depfiles commands config.status: executing libtool commands
╭─ caeri@ubuntu ~/lzp/GIT/asn1c ╰─ make make all-recursive make[1]: Entering directory '/home/caeri/lzp/GIT/asn1c' Making all in libasn1common ...... make[2]: Entering directory '/home/caeri/lzp/GIT/asn1c' make[2]: Leaving directory '/home/caeri/lzp/GIT/asn1c' make[1]: Leaving directory '/home/caeri/lzp/GIT/asn1c'
编译成功后,执行如下指令,进行安装
make install
╭─ caeri@ubuntu ~/lzp/GIT/asn1c master INSERT ╰─ make install Making install in libasn1common ...... /usr/bin/mkdir -p '/usr/local/share/doc/asn1c' /usr/bin/install -c -m 644 README.md INSTALL.md REQUIREMENTS.md FAQ ChangeLog BUGS '/usr/local/share/doc/asn1c' make[2]: Leaving directory '/home/caeri/lzp/GIT/asn1c' make[1]: Leaving directory '/home/caeri/lzp/GIT/asn1c'
执行
man asn1c
检查是否安装成功,如果有相关手册打印出来即为安装成功。asn1c(1) Version 0.9.29 asn1c(1) NAME asn1c -- the ASN.1 Compiler SYNOPSIS asn1c [-E [-F] | -P | -R] [-Sdir] [-X] [-Wdebug-...] [-foption] [-gen-option] [-pdu={all|auto|Type}] [-print-option] input-filenames... DESCRIPTION asn1c compiles ASN.1 specifications into a set of target language (C/C++) encoders and decoders for BER, DER, PER, XER, OER and other encoding rules.
快速开始示例
1.1 一个“Rectangle”转换和调试
最常见的需求是为一个已经存在的ASN.1数据文件,创建一个分析工具。让我们来为一个存在的Rectangle的二进制文件构建一个BER、OER、PER、XER(XML)的转换器。如下:
在
ans1c
文件夹下,新建一个rectangle.asn 文件,写入如下内容:RectangleModule DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER } END
执行如下指令,使用asn1c将其编译成.c和.h文件
asn1c -no-gen-example rectangle.asn
执行如下指令,生成一个转换器:
make -f converter-example.mk
完成后,使用二进制文件进行转换:
./converter-example -h
效果如下
╭─ caeri@ubuntu ~/lzp/GIT/asn1c master ? INSERT SIGINT(2) ↵ ╰─ ./converter-example -h Usage: ./converter-example [options] <datafile> ... Where options are: -iber Input is in BER (Basic Encoding Rules) or DER -ioer Input is in OER (Octet Encoding Rules) -iper Input is in Unaligned PER (Packed Encoding Rules) (DEFAULT) -ixer Input is in XER (XML Encoding Rules) -oder Output as DER (Distinguished Encoding Rules) -ooer Output as Canonical OER (Octet Encoding Rules) -oper Output as Unaligned PER (Packed Encoding Rules) -oxer Output as XER (XML Encoding Rules) (DEFAULT) -otext Output as plain semi-structured text -onull Verify (decode) input, but do not output -per-nopad Assume PER PDUs are not padded (-iper) -p <PDU> Specify PDU type to decode -p list List available PDUs -1 Decode only the first PDU in file -b <size> Set the i/o buffer size (default is 8192) -c Check ASN.1 constraints after decoding -d Enable debugging (-dd is even better) -n <num> Process files <num> times -s <size> Set the stack usage limit (default is 30000)
1.2 一个“Rectangle”编码器
这个示例将帮助你新增一个“Rectangle”的BER、XER编码器。
为了方便查看文件,我们在asn1c下新建asn_files和out两个文件夹,分别用来存放asn源文件和输出文件;
在
asn_files
下增加一个文件rectangle.asn ,内容如下:RectangleModule DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER } END
执行如下指令,使用asn1c将其编译成.c和.h文件
asn1c -no-gen-example -D out asn_files/rectangle.asn
到这里,就可以在out文件夹下得到多个文件,其中包含了Rectangle.c 和Rectangle.h。
创建一个包含main.c的文件,在main()函数中创建结构体
Rectangle_t
,然后使用BER、XER编码规则进行编码。main.c内容如下:/* * @Author : lv zhipeng * @Date : 2022-06-09 16:10:41 * @LastEditors : lv zhipeng * @LastEditTime : 2022-06-09 16:11:55 * @FilePath : /asn1c/out/main.c * @Description : * */ #include <Rectangle.h> /* Rectangle ASN.1 type */ #include <stdio.h> #include <sys/types.h> /* Write the encoded output into some FILE stream. */ static int write_out(const void *buffer, size_t size, void *app_key) { FILE *out_fp = app_key; size_t wrote = fwrite(buffer, 1, size, out_fp); return (wrote == size) ? 0 : -1; } int main(int ac, char **av) { Rectangle_t *rectangle; /* Type to encode */ asn_enc_rval_t ec; /* Encoder return value */ /* Allocate the Rectangle_t */ rectangle = calloc(1, sizeof(Rectangle_t)); /* not malloc! */ if(!rectangle) { perror("calloc() failed"); exit(1); } /* Initialize the Rectangle members */ rectangle->height = 42; /* any random value */ rectangle->width = 23; /* any random value */ /* BER encode the data if filename is given */ if(ac < 2) { fprintf(stderr, "Specify filename for BER output\n"); } else { const char *filename = av[1]; FILE *fp = fopen(filename, "wb"); /* for BER output */ if(!fp) { perror(filename); exit(1); } /* Encode the Rectangle type as BER (DER) */ ec = der_encode(&asn_DEF_Rectangle, rectangle, write_out, fp); fclose(fp); if(ec.encoded == -1) { fprintf(stderr, "Could not encode Rectangle(at % s)\n", ec.failed_type ? ec.failed_type->name : "unknown"); exit(1); } else { fprintf(stderr, "Created % s with BER encoded Rectangle\n", filename); } } /* Also print the constructed Rectangle XER encoded (XML) */ xer_fprint(stdout, &asn_DEF_Rectangle, rectangle); return 0; /* Encoding finished successfully */ }
这里有一个asn1c的bug,需要从asn1c源码目录手动复制这几个文件到out目录再编译,否则需要禁用相关OER的编解码,具体解决方案可以看这里
cd out cp ../skeletons/BIT_STRING* ./ cp ../skeletons/OCTET_STRING* ./
编译所有文件,生成可执行文件rencode。
cc -I. -o rencode *.c
这个rencode程序,就是支持BER、XER编码的Rectangle编码器,运行程序可以看到我们在
main()
函数中填充的数据XER格式的输出:╭─ caeri@ubuntu ~/lzp/GIT/asn1c/out master ? INSERT ✔ ╰─ ./rencode Specify filename for BER output <Rectangle> <height>42</height> <width>23</width> </Rectangle>
1.3 一个“Rectangle”解码器
这个示例将帮助你新增一个“Rectangle”的BER、XER解码器。
新建一个名为rectangle.asn 的文件。内容如下:
RectangleModule DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER, width INTEGER } END
执行如下指令,使用asn1c将其编译成.c和.h文件
asn1c -no-gen-example -D out asn_files/rectangle.asn
到这里,就可以在当前文件夹下得到多个文件,其中包含了Rectangle.c 和Rectangle.h。
创建一个main.c文件,包含main()函数,该函数接收一个二进制输入文件,它将输入文件按照BER编码规则进行解码为Rectangle 格式,然后会打印输出XML格式的内容。内容如下:
#include <Rectangle.h> /* Rectangle ASN.1 type */ #include <stdio.h> #include <sys/types.h> int main(int ac, char **av) { char buf[1024]; /* Temporary buffer */ asn_dec_rval_t rval; /* Decoder return value */ Rectangle_t *rectangle = 0; /* Type to decode. Note this 01! */ FILE *fp; /* Input file handler */ size_t size; /* Number of bytes read */ char *filename; /* Input file name */ /* Require a single filename argument */ if(ac != 2) { fprintf(stderr, "Usage: % s<file.ber>\n", av[0]); exit(1); } else { filename = av[1]; } /* Open input file as readିonly binary */ fp = fopen(filename, "rb"); if(!fp) { perror(filename); exit(1); } /* Read up to the buffer size */ size = fread(buf, 1, sizeof(buf), fp); fclose(fp); if(!size) { fprintf(stderr, "% s: Empty or broken\n", filename); exit(1); } /* Decode the input buffer as Rectangle type */ rval = ber_decode(0, &asn_DEF_Rectangle, (void **)&rectangle, buf, size); if(rval.code != RC_OK) { fprintf(stderr, "% s: Broken Rectangle encoding at byte% ld\n ", filename,(long)rval.consumed); exit(1); } /* Print the decoded Rectangle type as XML */ xer_fprint(stdout, &asn_DEF_Rectangle, rectangle); return 0; /* Decoding finished successfully */ }
编译所有文件,生成可执行文件rencode(注意参考1.2复制缺少的文件)。
cc -I. -o rdecode *.c
这个
rdncode
程序,就是支持BER
、XER
编码的Rectangle解码器。
1.4 给“Rectangle”增加约束(数据验证)
此示例显示如何向 ASN.1 规范添加基本约束以及如何调用应用程序中的约束验证代码。
创建名为rectangle.asn的文件。内容如下:
这里我们对asn的使用手册中rectangle.asn进行了修改,将
width INTEGER (0..MAX)
修改为width INTEGER (0..200)
,因为在使用MAX时,没有指定具体的最大值,所以在后边的asn_check_constraints()
时发生错误。RectangleModuleWithConstraints DEFINITIONS ::= BEGIN Rectangle ::= SEQUENCE { height INTEGER (0..100), -- Value range constraint width INTEGER (0..200) -- Makes width non-negative } END
参考1.3中节,编译文件生成.c和.h。
asn1c -no-gen-example -D out asn_files/rectangle.asn
新建main.c,将1.2 一个“Rectangle”编码器中的
main.c
复制过来,然后将如下代码加入到处理程序末尾,部分如下:/* Also print the constructed Rectangle XER encoded (XML) */ xer_fprint(stdout, &asn_DEF_Rectangle, rectangle); int ret;/* Return value */ char errbuf[128]; /* Buffer for error message */ size_t errlen = sizeof(errbuf); /* Size of the buffer */ /* ... here goes the Rectangle decoding code ... */ ret = asn_check_constraints(&asn_DEF_Rectangle, rectangle, errbuf, &errlen); /* assert(errlen < sizeof(errbuf)); // you may rely on that */ if(ret) { fprintf(stderr, "Constraint validation failed : % s\n", errbuf); /* exit(...); // Replace with appropriate action */ } /* ... here goes the Rectangle encoding code ... */
像上一节那样编译所有文件,这里主要要复制
cd out cp ../skeletons/BIT_STRING* ./ cp ../skeletons/OCTET_STRING* ./ cc -I. -o rencode *.c
这里我们进行两次实验,分别让填充数据合法和超限,观察程序的检测结果:
第一次,数据合法
填充合法数据
/* Initialize the Rectangle members */ rectangle->height = 15; /* any random value */ rectangle->width = 30; /* any random value */
执行结果
╭─ caeri@ubuntu ~/lzp/GIT/asn1c/out master ? INSERT SIGINT(2) ↵ ╰─ ./rencode Specify filename for BER output <Rectangle> <height>15</height> <width>30</width> </Rectangle>
第二次,数据超限
填充超限数据
/* Initialize the Rectangle members */ rectangle->height = 150; /* any random value */ rectangle->width = 300; /* any random value */
执行结果
╭─ caeri@ubuntu ~/lzp/GIT/asn1c/out master ? INSERT ✔ ╰─ ./rencode Specify filename for BER output <Rectangle> <height>150</height> <width>300</width> </Rectangle> Constraint validation failed : INTEGER: constraint failed (Rectangle.c:30)
至此,数据验证演示完毕。
ASN.1编译器
2.1 ASN1C编译工具
ASN.1编译器是将ASN.1表示语法中的规范转换为其他一些语言,例如C语言。
编译器读取(输入)规范,发出(输出)一系列目标语言的结构(struct, union, enum )来实现相应的ASN.1类型。编译器还会创建支持这些结构体序列化/反序列化的代码,代码支持BER、DER、OER、PER、XER的编码规则。
例如这个ASN.1文件:
RectangleModule DEFINITIONS ::= BEGIN
Rectangle ::= SEQUENCE {
height INTEGER, -- Height of the rectangle
width INTEGER --Width of the rectangle
}
END
编译器读取上述的ASN.1定义,并生成以下C结构体:
typedef struct Rectangle_s {
long height;
long width;
} Rectangle_t;
asn1c 编译器还创建了用于将此结构转换为独立于平台的解码器代码,解码代码支持将解码内容返回到当前设备。
用于编译 ASN.1 模块:
asn1c <modules.asn>
如果多个ASN.1模块包含互相依赖的关系,编译的时候需要包含多个asn文件:
asn1c <module1.asn> <module2.asn> ...
编译器的参数-E和-EF用来检测解析和语义错误。这些选项将指示编译器按照编译器所理解的进行转换被识别为正确的(-F 参数会尝试修复ASN.1文件中的语法问题)ASN.1 规范,这个参数可以用来检查编译器是否正确支持当前的语法构造。
asn1c -EF <module-to-test.asn>
-P 选项用于将编译后的输出打印到屏幕上,而不是创建一堆当前目录中磁盘上的 .c 和 .h 文件。
另一个选项 -R,询问编译器只生成需要生成的文件,并阻止生成大量链接支持文件。
打印编译输出而不是创建多个源文件:
asn1c -P <module-to-compile-and-print.asn>
2.2 编译器输出
asn1c编译器会输出一些文件,包括:
- .asn文件中定义的每种ASN类型对应的.c和.h文件。这些文件名的命名类似于ASN.1格式,(例如第一章提到的RectangleModule ASN.1 模块的 Rectangle.c 和 Rectangle.h)。
- 一些其他辅助程序的.c和.h文件,例如通用的编码器、解码器和其他有用的demo。
- 一个Makefile.am.libasncodecs文件,它显式的列出了所有生成的文件。这个makefile可以用来编译编解码库。
- 一个converter-example.c 文件,它包含了main()函数,支持编码和数据格式转换。它可以将给定的PDU文件在BER、XER、OER和PER之间进行转换。有时候我们的应用代码就是在这个文件基础上进行修改。
- 一个converter-example.mk 文件,该makefile文件通过编译Makefile.am.libasncodecs 和converterexample.c 文件,为你的ASN数据格式生成一个转换和调试程序。
只需要几条指令就可以编译所有内容:
asn1c -pdu=Rectangle *.asn
make -f converte-example.mk # If you use ‘make‘
这里有一个小的技巧,因为我们在第一章中,输入的asn文件在asn_files文件夹下,输出在out下,因此我这里编译的时候需要将Makefile.am.libasncodec复制到out下,然后修改converter-example.mk如下部分:
#修改前
include out/Makefile.am.libasncodec
ASN_PROGRAM_SRCS ?= \
out/converter-example.c
#修改后
include Makefile.am.libasncodec
ASN_PROGRAM_SRCS ?= \
converter-example.c
2.3 命令行配置
下边表格总结了ASN1C的命令行选项。
阶段选择选项 | 描述 |
---|---|
-E | 在执行解析阶段后停止,并打印重建的 ASN.1规范代码到标准输出。 |
-F | 和-E选项一起使用,指示编译器在 ASN.1 语法树修复阶段后停止并将重构的 ASN.1 规范转储到标准输出。 |
-P | 将编译后的输出打印到标准输出,而不是在磁盘上创建目标语言文件。 |
-R | 限制编译器只生成 ASN.1 表,省略通常的支持代码。 |
-S | 指定文件夹下的ASN.1文件。 |
-X | 为指定的ASN.1模块生成XML DTD。 |
警告选项 | 描述 |
---|---|
-Werror | 将警告视为错误; 如果产生任何警告则中止 |
-Wdebug-parser | 在 ASN.1 解析阶段启用解析器调试 |
-Wdebug-lexer | 在 ASN.1 解析阶段启用词法分析器调试。 |
-Wdebug-fixer | 在修复过程中启用 ASN.1 语法树修复器调试器。 |
-Wdebug-compiler | 在实际编译时启用调试 |
语言配置 | 描述 |
---|---|
-fbless-SIZE | *允许INTEGER, ENUMERATED和其他类型的约束,通常为了防止数据数值范围超出导致错误禁止该选项,即数值超出范围不会报错。*这违反了ASN.1标准,可能导致生成的代码没有用。待验证 |
-fcompound-names | 对 C 结构使用复杂的名称。如果模块在多个上下文中重用相同的标识符,则使用复杂的名称可以防止名称冲突 |
-findirect-choice | 为 CHOICE 类型生成代码时,编译 CHOICE成员作为间接指针而不是内联声明。考虑将此选项与 -fno-includedeps 一起使用以防止循环引用。 |
-fincludes-quoted | 在“double”而不是 引号中生成 #include 行。 |
-fknown-extern-type= | 假装指定的类型是已知的。 编译器会假设给定类型的目标语言源文件已手动提供。 |
-fline-refs | 在生成的代码注释中包含 ASN.1 模块的行号。 |
-fno-constraints | 不产生ASN.1的类型约束代码片段,这样可执行文件小一点 。 |
-fno-include-deps | 不为非关键项生成#include包含。 |
-fwide-types | 使用泛类型定义(INTEGER_t, REAL_t ),而不是机器自身的类型定义(long, double )。 |
-no-gen-OER | 不生成OER支持代码 |
-no-gen-PER | 不生成PER支持代码 |
-no-gen-example | 不生产ASN.1格式转换demo。 |
-pdu={all|all|Type | 为指定类型|所有类型 创建 PDU 表 |
输出选项 | 描述 |
---|---|
-print-class-matrix | 当配置了-EF选项,这个选项指示编译器打印收集到的ASN对象矩阵。 |
-print-constraints | 当配置了-EF选项,此选项指示编译器打印其对子类型约束的内部理解。 |
-print-lines | 在 -E 输出中生成“– #line”注释 |
API参考
本章中描述的函数供开发人员使用。这些功能在下一个主要版本之前不太可能改变或被删除。
此处未列出的 API 调用不是公开的,不应由应用程序调用。
这里我们从手册中摘取基本的asn_xxx_xxx()系列函数进行说明,其他编解码类型的功能和调用方式类似,不再此赘述。
3.1 宏定义 ASN_STRUCT_FREE() macro
概要
#define ASN_STRUCT_FREE(type_descriptor, struct_ptr)
描述
递归释放由type_descriptor描述结构体占用内存。struct_ptr指针为要释放的结构体指针。
当struct_ptr为NULL时,不执行任何操作。
返回值
无
示例
Rectangle_t *rect = ...;
ASN_STRUCT_FREE(asn_DEF_Rectangle, rect);
3.2 宏定义 ASN_STRUCT_RESET()
概要
#define ASN_STRUCT_RESET(type_descriptor, struct_ptr)
描述
清空结构体指针struct_ptr的成员内容,但是不释放指针本身(还指向原来的内存,而不是指向NULL)
当 struct_ptr 为 NULL 时什么也不做。
返回值
无
示例
struct my_figure { /* The custom structure */
int flags; /* <some custom member> */
/* The type is generated by the ASN.1 compiler */
Rectangle_t rect;
/* other members of the structure */
};
struct my_figure *fig = ...;
ASN_STRUCT_RESET(asn_DEF_Rectangle, &fig->rect);
3.3 asn_check_constraints()
概要
int asn_check_constraints(
const asn_TYPE_descriptor_t *type_descriptor,
const void *struct_ptr, /* Target language’s structure */
char *errbuf, /* Returned error description */
size_t *errlen /* Length of the error description */
);
描述
根据 ASN.1 规则进行数据验证。
errbuf 和 errlen 可能会被赋值,在调用此函数之前,它们应指向适当的缓冲区空间及其长度。
如果约束验证失败,则 errlen将包含 errbuf 中用于编码错误消息的实际字节数。 如果正确,则返回0.
返回值
数据校验通过返回0,否则返回-1
示例
Rectangle_t *rect = ...;
char errbuf[128]; /* Buffer for error message */
size_t errlen = sizeof(errbuf); /* Size of the buffer */
int ret = asn_check_constraints(&asn_DEF_Rectangle, rectangle, errbuf, &errlen);
/* assert(errlen < sizeof(errbuf)); // Guaranteed: you may rely on that */
if(ret) {
fprintf(stderr, ”Constraint validation failed: %s\n”, errbuf);
}
3.4 asn_decode()
概要
asn_dec_rval_t asn_decode(
const asn_codec_ctx_t *opt_codec_ctx,
enum asn_transfer_syntax syntax,
const asn_TYPE_descriptor_t *type_descriptor,
void **struct_ptr_ptr,/* Pointer to a target structure’s ptr */
const void *buffer, /* Data to be decoded */
size_t size /* Size of that buffer */
);
描述
根据syntax语法规则,解析大小为sizede的buffer为一个type_descriptor 类型的结构体消息。
struct_ptr_ptr 必须指向包含指向正在解码的结构(即buffer中的相同结构)的指针的内存位置。 最初通常设置 *struct_ptr_ptr 指针 到0。在这种情况下,asn_decode() 将为结构动态分配内存在解析过程中根据需要添加其成员。 如果 *struct_ptr_ptr 已经指向一些内存,asn_decode() 将根据需要分配后续成员解析 。
返回值
在不成功终止时,*struct_ptr_ptr 可能包含部分解码数据。 此数据可能对调试有用(例如通过使用 asn_fprint())。务必通过调用 ASN_STRUCT_FREE() 或 ASN_STRUCT_RESET()释放内存。
成功的话会返回一个如下这样一个结构体:
typedef struct {
enum {
RC_OK, /* Decoded successfully */
RC_WMORE, /* More data expected, call again */
RC_FAIL /* Failure to decode data */
} code; /* Result code */
size_t consumed; /* Number of bytes consumed */
} asn_dec_rval_t;
重启特性(Restartability)
有一些传输语法解析器(ATS_BER))支持重启。
这意味着如果缓冲区的数据少于预期,asn_decode() 将处理可用的任何内容,并要求使用 RC_WMORE 返回 .code 提供更多数据。
请注意,在 RC_WMORE 情况下,解码器处理的数据可能比缓冲区中可用的数据少,这意味着您必须能够安排下一个缓冲区以包含前一个缓冲区未处理的部分。
RC_WMORE 代码可能仍由不支持可重启性的解析器返回。在这种情况下,部分解码的结构将被丢弃,下一次调用应使用扩展缓冲区从开始进行解析。
示例
Rectangle_t *rect = 0; /* Note this 01! */
asn_dec_rval_t rval;
rval = asn_decode(0, ATS_BER, &asn_DEF_Rectangle, (void **)&rect, buffer, buf_size);
switch(rval.code) {
case RC_OK:
asn_fprint(stdout, &asn_DEF_Rectangle, rect);
ASN_STRUCT_FREE(&asn_DEF_Rectangle, rect);
break;
case RC_WMORE:
case RC_FAIL:
default:
ASN_STRUCT_FREE(&asn_DEF_Rectangle, rect);
break;
}
参考
asn_fprint()
3.5 ask_encode()
概要
#include <asn_application.h>
asn_enc_rval_t asn_encode(
const asn_codec_ctx_t *opt_codec_ctx,
enum asn_transfer_syntax syntax,
const asn_TYPE_descriptor_t *type_to_encode,
const void *structure_to_encode,
asn_app_consume_bytes_f *callback, void *callback_key);
描述
将输入的type_to_encode结构体类型的消息structure_to_encode,以syntax的方式进行编码,通过callback函数对序列化后的数据进行处理,其中callback_key参数会被传入到callback函数。
回调的格式声明如下:
typedef int(asn_app_consume_bytes_f)(const void *buffer, size_t size, void *callback_key);
返回值
返回一个复合的结构体:
typedef struct {
ssize_t encoded;
const asn_TYPE_descriptor_t *failed_type;
const void *structure_ptr;
} asn_enc_rval_t;
如果编码不成功,.encoded 成员设置为 -1
如果编码成功,.encoded成员指定序列化输出的大小。
无论选择的 ASN.1 传输语法如何,都以字节为单位返回序列化输出大小。
出错时(当 .encoded 设置为 -1 时),errno 设置为以下值之一:
- EINVAL 函数参数不正确,例如NULL。
- ENOENT 编码传输语法未定义。
- EBADF 结构体无效或内容限制失败。
- EIO 在回调函数编码时返回了负值。
示例
static int save_to_file(const void *data, size_t size, void *key)
{
FILE *fp = key;
return (fwrite(data, 1, size, fp) == size) ? 0 : ି1;
}
Rectangle_t *rect = ...;
FILE *fp = ...;
asn_enc_rval_t er;
er = asn_encode(0, ATS_DER, &asn_DEF_Rectangle, rect, save_to_file, fp);
if(er.encoded == -1) {
fprintf(stderr, ”Failed to encode % s\n”, asn_DEF_Rectangle.name);
} else {
fprintf(stderr, ”% s encoded in % zd bytes\n”, asn_DEF_Rectangle.name,
er.encoded);
}
3.6 asn_encode_to_buffer()
概要
#include <asn_application.h>
asn_enc_rval_t asn_encode_to_buffer(
const asn_codec_ctx_t *opt_codec_ctx,
enum asn_transfer_syntax syntax,
const asn_TYPE_descriptor_t *type_to_encode,
const void *structure_to_encode,
void *buffer, size_t buffer_size);
描述
将输入的type_to_encode结构体类型的消息structure_to_encode,以syntax的方式进行编码至大小为buffer_siz的ebuffer中。
返回值
typedef struct {
ssize_t encoded;
const asn_TYPE_descriptor_t *failed_type;
const void *structure_ptr;
} asn_enc_rval_t;
不成功时,encoded被设置为-1;
成功时,encoded被设置为实际编码的字节数;
示例
Rectangle_t *rect = ...;
uint8_t buffer[128];
asn_enc_rval_t er;
er = asn_encode_to_buffer(0, ATS_DER, &asn_DEF_Rectangle, rect, buffer,
sizeof(buffer));
if(er.encoded == -1) {
fprintf(stderr, ”Serialization of % s failed.\n”,
asn_DEF_Rectangle.name);
} else if(er.encoded > sizeof(buffer)) {
fprintf(stderr, ”Buffer of size %zu is too small for %s, need %zu\n”,
buf_size, asn_DEF_Rectangle.name, er.encoded);
}
3.7 asn_encode_to_new_buffer()
概要
#include <asn_application.h>
typedef struct {
void *buffer;
/* NULL if failed to encode. */
asn_enc_rval_t result;
} asn_encode_to_new_buffer_result_t;
asn_encode_to_new_buffer_result_t asn_encode_to_new_buffer(
const asn_codec_ctx_t *opt_codec_ctx,
enum asn_transfer_syntax syntax,
const asn_TYPE_descriptor_t *type_to_encode,
const void *structure_to_encode);
描述
将type_to_encode类型的结构体structure_to_encode,以syntax编码方式进行编码,返回值带有编码的buffer。
返回值
失败时,buffer被设置为NULL,encoded被设置为-1;
errno如下:
- EINVAL Incorrect parameters to the function, such as NULLs
- ENOENT Encoding transfer syntax is not defined (for this type)
- EBADF The structure has invalid form or content constraint failed
- ENOMEM Memory allocation failed due to system or internal limits
示例
asn_encode_to_new_buffer_result_t res;
res = asn_encode_to_new_buffer(0, ATS_DER, &asn_DEF_Rectangle, rect);
if(res.buffer) {
/* Encoded successfully. */
free(res.buffer);
} else {
fprintf(stderr, ”Failed to encode %s, estimated %zd bytes\n”,
asn_DEF_Rectangle.name, res.result.encoded);
}
3.8 asn_fprint()
概要
int asn_fprint(FILE *stream,
/* Destination file */
const asn_TYPE_descriptor_t *type_descriptor,
const void *struct_ptr
/* Structure to be printed */
);
描述
asn_fprint() 函数打印目标语言的人类可读描述结构到由流指针指定的文档流中。
输出格式不符合任何标准。
即使对于不完整和破碎的结构体,asn_fprint() 函数尝试生成有效的输出,这使得它更适合调试复杂的案例,而不是xer_fprint()。
返回值
成功 0;
失败 -1;
示例
Rectangle_t *rect = ...;
asn_fprint(stdout, &asn_DEF_Rectangle, rect);
3.9 asn_random_fill()
概要
int asn_random_fill(
const asn_TYPE_descriptor_t *type_descriptor,
void **struct_ptr_ptr,/* Pointer to a target structure’s ptr */
size_t approx_max_length_limit
);
描述
根据type_descriptor类型规范,填充一个随机的struct_ptr_ptr结构体。
为了获得最佳结果,生成的代码应该在没有 -no-gen-PER
选项的情况下提供给 asn1c,使用 -no-gen-PER
选项可能会导致生成的值超出边界;
approx_max_length_limit
指定结果的近似限制结构以与字节非常相似的单位表示。实际结果可能要大几倍或小于给定的长度限制。为此选择初始值的经验,则参数是采用典型结构并使用其 DER 输出大小的两倍
返回值
- 0 Structure was properly initialized with random data
- -1 Failure to initialize the structure with random data
API使用示例
首先,我们在进程中包含必要的头文件。
在Rectangle中,我们包含 Rectangle.h就足够了。
#include <Rectangle.h>
这个头文件定义了一个ASN定义的 rectangle对应的C结构体,一个被在很多API中引用的全局的类型描述符,类型描述符一般以asn_DEF_
开头,例如:
Rectangle_t *rect = ...;
ASN_STRUCT_FREE(asn_DEF_Rectangle, rect);
这里定义了一个Rectangle_t
类型的指针rect
,这个指针使用ASN_STRUCT_FREE
释放。
第二行调用了ASN_STRUCT_FREE()宏,这个宏调用Rectangle_t
对应的结构体释放程序。
4.1 通用编解码
在了解特殊的编解码之前,先介绍一下通用编解码方式。
asn1c运行时支持两个高等级的接口asn_encode*
和asn_decode*
,可以用参数来选择编解码的语法规则。定义如下:
/*
* A selection of ASN.1 Transfer Syntaxes to use with generalized
* encoders and decoders declared further in this .h file.
*/
enum asn_transfer_syntax {
/* Avoid appearance of a default transfer syntax. */
ATS_INVALID = 0,
/* Plaintext output (not conforming to any standard), for debugging. */
ATS_NONSTANDARD_PLAINTEXT,
/* Returns a randomly generatede structure. */
ATS_RANDOM,
/*
* X.690:
* BER: Basic Encoding Rules.
* DER: Distinguished Encoding Rules.
* CER: Canonical Encoding Rules.
* DER and CER are more strict variants of BER.
*/
ATS_BER,
ATS_DER,
ATS_CER, /* Only decoding is supported */
/*
* X.696:
* OER: Octet Encoding Rules.
* CANONICAL-OER is a more strict variant of BASIC-OER.
*/
ATS_BASIC_OER,
ATS_CANONICAL_OER,
/*
* X.691:
* PER: Packed Encoding Rules.
* CANONICAL-PER is a more strict variant of BASIC-PER.
* NOTE: Produces or consumes a complete encoding (X.691 (08/2015) #11.1).
*/
ATS_UNALIGNED_BASIC_PER,
ATS_UNALIGNED_CANONICAL_PER,
/*
* X.693:
* XER: XML Encoding Rules.
* CANONICAL-XER is a more strict variant of BASIC-XER.
*/
ATS_BASIC_XER,
ATS_CANONICAL_XER
};
使用这个编解码的选择器,使编解码过程变得很通用。
编码
uint8_t buffer[128];
size_t buf_size = sizeof(buffer);
asn_enc_rval_t er;
er = asn_encode_to_buffer(0, ATS_DER, &asn_DEF_Rectangle, buffer, buf_size);
if(er.encoded > buf_size) {
fprintf(stderr, ”Buffer of size %zu is too small for %s, need %zu\n”,
buf_size, asn_DEF_Rectangle.name, er.encoded);
}
解码
Rectangle_t *rect = 0;
... = asn_decode(0, ATS_BER, &asn_DEF_Rectangle, (void **)&rect,buffer, buf_size);
4.2 BER解码
基本编码规则描述了(由 ASN.1 社区)使用最广泛的方法以独立于机器的方式对给定结构进行编码和解码。其他几种编码规则(CER,DER)定义了限制性更强的BER版本,因此,通用 BER 解析器还能够解码由 CER 和 DER 编码器编码的数据。反之不可以。
ASN.1 编译器提供了通用 BER 解码器,能够解码 BER、CER和 DER 编码数据。
解码器是可重新启动的(面向数据流),意味着当buffer中的数据少于期望的数据,解码器将处理有效数据,然后在返回值中使.code
为RC_WMORE
来请求更多数据。
注意,在RC_WMORE
这种情况下,实际处理的数据可能会比实际情况要少(也就是不一定会完全处理完buffer中提供的所有数据),因此在下一个buffer中需要包含上一个buffer中未处理的数据。
假设,有2个要编码的buffer,分别是100Bytes和200Bytes。
- 你可以使用一个300Bytes的buffer来代替这2个buffer的内容;
- 或者你可以像解码器提供一个100Bytes的buffer,它可能只会处理95Bytes;则下一次提供的buffer要提供5+200Bytes的buffer,其中的5Bytes是第一次没有处理的部分。
这并不像它可能的那样方便(BER编码器可能会消耗整个100字节并将这5个字节保存在一些临时存储中),但在现有基于流的情况下处理它实际上可能非常适合现有的算法。
这里有一个简单的示例:
Rectangle_t * simple_deserializer(const void *buffer, size_t buf_size)
{
asn_dec_rval_t rval;
Rectangle_t *rect = 0;
rval = asn_DEF_Rectangle.op->ber_decoder(0,&asn_DEF_Rectangle,(void **)&rect,buffer, buf_size, 0);
if(rval.code == RC_OK) {
return rect;
/* Decoding succeeded */
} else {
/* Free the partially decoded rectangle */
ASN_STRUCT_FREE(asn_DEF_Rectangle, rect);
return 0;
}
}
上边这段代码定义了一个函数simple_deserializer()
,它需要给定一个buffer和bufferd的大小,然后预计会返回一个指向Rectangle_t
类型的指针。在内部,它试图通过ber_decoder()
解析buffer中的内容到一个结构体rect
。如果ber_decoder()
函数没有完成解码,它会返回0(no data),并且释放内存(这里的释放是必须的).
稍微简单一点的调用,是使用全局函数ber_decode()
。
rval = ber_decode(0, &asn_DEF_Rectangle, (void **)&rect, buffer,buf_size);
这里注意使用ber_decode()调用时,最后的参数0不必要。
这两种调用方式是完全等效的。
BER decoder 的解码也可能会失败,返回值:
- RC_WMORE: 需要在下一个数据流提供更多的数据
- RC_FAIL: 解码失败
- 其他
4.3 DER编码
可分辨编码规则是 BER 编码规则的规范变体。DER最适合于对事先知道所有长度的结构进行编码。
与 BER 解码器一样,DER 编码器可以直接从 ASN.1 类型调用描述符(asn_DEF_Rectangle),这更简单一些:
/*
* This is the serializer itself.
* It supplies the DER encoder with the
* pointer to the custom output function.
*/
ssize_t simple_serializer(FILE *ostream, Rectangle_t *rect)
{
asn_enc_rval_t er; /* Encoder return value */
er = der_encode(&asn_DEF_Rect, rect, write_stream, ostream);
if(er.encoded == -1) {
fprintf(stderr, ”Cannot encode %s: %s\n”,
er.failed_type ->name, strerror(errno));
return -1;
} else {
/* Return the number of bytes */
return er.encoded;
}
}
正如我们所看到的,der_encode()
不会将写入任何buffer,而是通过调用自定义的函数(可能会调用多次),将数据进行适当的存储。app_key
参数对于der_encode()
是不可见的,只由自定义函数来调用。
如果未给出自定义写入函数(作为 0 传递),则 DER 编码器基本上将执行相同的操作(即对数据进行编码),但不会调用任何回调(数据无处可去)。在确定结构编码的大小之前可能很有用.
4.4 XER编码
XER全称是XML Encoding Rules,即XML编码规则。XML是一种基本的信息交换格式,是一种可拓展的标记语言。编码器有两种类型:stdio-base和回调。使用回调的方式,编码器的处理与DER类似,我们在4.3章节有介绍过。以下示例使用write_stream()
的定义:
/*
* This procedure generates an XML document
* by invoking the XER encoder.
* NOTE: Do not copy this code verbatim!
*
If the stdio output is necessary,
*
use the xer_fprint() procedure instead.
*
See section 4.7 on page 62.
*/
int print_as_XML(FILE *ostream, Rectangle_t *rect)
{
asn_enc_rval_t er; /* Encoder return value */
er = xer_encode(&asn_DEF_Rectangle, rect,
XER_F_BASIC, /* BASIC-XER or CANONICAL-XER */
write_stream, ostream);
return (er.encoded ==-1) ? ି1 : 0;
}
4.5 XER解码
使用XER编码的数据,随后就可以使用xer_decode()
来解码:
Rectangle_t * XML_to_Rectangle(const void *buffer, size_t buf_size)
{
asn_dec_rval_t rval;
Rectangle_t *rect = 0;
rval = xer_decode(0, &asn_DEF_Rectangle, (void **)&rect,buffer, buf_size);
if(rval.code == RC_OK)
{
return rect;
/* Decoding succeeded */
} else
{
/* Free partially decoded rect */
ASN_STRUCT_FREE(asn_DEF_Rectangle, rect);
}
return 0;
}
解码器采用BASIC-XER和CANONICAL-XER编码.
4.6 验证目标结构体
有时候结构体需要验证数据的有效性。例如,ASN.1中定义的必填字段是否被填上;另一方面,成功解码的消息数据有可能数据是有问题的。
asn_check_constraints()
函数检查类型中是否存在各种隐式和显示约束条件。建议在每次解码之后和编码之前使用该接口进行校验。
4.7 打印目标结构体
在调试时,打印目标结构体,使用asn_fprint()
函数。
asn_fprint(stdout, &asn_DEF_Rectangle, rect);
一个使用的替代方案是使用XML格式。默认的 BASIC-XER 编码器对输出执行合理的格式化。既有用又可读。使用 xer_fprint()
函数:
xer_fprint(stdout, &asn_DEF_Rectangle, rect);
4.8 释放目标结构体
释放结构体要求结构体的所有子成员也被释放,ASN_STRUCT_FREE()
宏来完成这个功能。
但是,这并不总是可行的,在下边的例子中,程序定义了一个包含ASN.1结构的结构体:
struct my_figure { /* The custom structure */
int flags; /* <some custom member> */
/* The type is generated by the ASN.1 compiler */
Rectangle_t rect; /* other members of the structure */
};
如果还是用上述ASN_STRUCT_FREE()
宏来进行释放,则会情况rect成员。这种情况下,使用另外一个接口ASN_STRUCT_RESET()
。两种情况的调用情况如下:
/* 1. Rectangle_t is defined within my_figure */
struct my_figure {
Rectangle_t rect;
} *mf = ...;
/*
* Freeing the Rectangle_t
* without freeing the mfି >rect area.
*/
ASN_STRUCT_RESET(asn_DEF_Rectangle, &mf ି >rect);
/* 2. Rectangle_t is a stand-alone pointer */
Rectangle_t *rect = ...;
/*
* Freeing the Rectangle_t
* and freeing the rect pointer.
*/
ASN_STRUCT_FREE(asn_DEF_Rectangle, rect);