将架构编译成一组 C# 文件后,添加几行 main() 函数的代码,您可以构建您的编码应用程序。 架构版本必须引用生成的代码所依赖的编码器/解码器运行时库 (asn1csrt.dll)。
ASN.1 API
以下示例包含一个应用程序,该应用程序假定 MySch.asn 架构文件包含一个 MyMod 模块和一个 MyValue 值:
namespace SimpleApp { class Program { static void Main() { // instantiate encoder/decoder(s) var codec = new MySch.BerCodec(); // encode my value into a buffer byte[] buffer = codec.Encode(MySch.MyMod.Values.MyVal); } } }
大多数情况下,API 操作两种 ASN.1 对象:编解码器和 PDU(数据)。
OSS ASN.1 Tools for C# API 包含两种类型的命名空间:
Oss.Asn1 类作为 .NET 运行时库(程序集)实现,您的应用程序必须将其作为参考包含才能成功构建。
前面的示例向您展示了如何将值编码到缓冲区中; 但是,您也可以使用 System.IO 流和文件将其编码为 BER 文件:
使用 (Stream berfile = File.Create("PduEncoding.ber")) codec.Encode(pdu, berfile);
表示名称与模式名称匹配,类型遵循 ASN.1 类型,因此您可以轻松访问生成的数据。 原始类型由 int、bool 和 string 表示,而构造类型(SEQUENCE、CHOICE、ENUMERATED、BIT STRING 等)由包装类表示。 例如:
ASN.1 | C# |
---|---|
MyMod DEFINITIONS AUTOMATIC TAGS ::= BEGIN MyPdu ::= SEQUENCE { -- declaration of PDU fields ia5str IA5String (SIZE (1..60)), batting-average REAL, --<DECIMAL>-- handedness ENUMERATED { left-handed(-1), right-handed(240), ambidextrous(0)}, ch CHOICE { a [2] INTEGER, b [3] BOOLEAN, c [4] BIT STRING }, bstr BIT STRING uStr UniversalString, . . . |
var pdu = new Mysch.MyMod.MyPdu(); // setting PDU fields pdu.Ia5str = "Ia5str"; pdu.BattingAverage = 250e-3M; pdu.Handedness = Mysch.MyMod.MyPdu.HandednessType.RightHanded; pdu.Ch.A = 129; pdu.Bstr.Set(new byte[] {0xAA,0x55}); pdu.UStr = new int[] {0x1D161}; //U+1D161 MUSICAL SYMBOL . . . |
标记为 DEFAULT 或 OPTIONAL 的架构字段表示为 可为空 类型(原生 .NET 类型也可以为可为空,例如 int?、bool? 等 )。 您不需要传输可选值或默认值,因此您可以在编码/解码期间省略这些字段(通过将它们设置为 null)。 以下是使用此类字段的模式和应用程序的示例:
ASN.1 | C# |
---|---|
Order ::= SEQUENCE { id IA5String, cnt INTEGER DEFAULT 1, tax BOOLEAN OPTIONAL } |
public class Order : Oss.Asn1.BasePdu { public string Id { get; set; } public int? Cnt { get; set; } public static int DefaultCnt { get; } public bool? Tax { get; set; } . . . . } |
应用程序可以通过比较或使用字段的默认值进行编码/解码:
// Encoding if (Order.DefaultCnt.Equals(myOrd.Cnt)) // equals the default? myOrd.Cnt = null; // do not transmit codec.Encode(myOrd, stream); . . . . // Decoding codec.Decode(stream, myOrd); if (myOrd.Cnt == null) // was Cnt transmitted? myOrd.Cnt = Order.DefaultCnt; // No, use default value
注意: 出于性能原因,编解码器不会比较 DEFAULT 值来决定是否对其进行编码。 应用程序有责任在编码期间忽略这些值(将它们设置为空),以便它们不会被传输。
您可以在对象构造期间使用 C# 初始化器语法设置数据,如以下示例所示,该示例初始化上面显示的 CHOICE 元素的 BIT STRING 成员:
var bits = new MyPdu.ChType() { C = new Oss.Asn1.BitString ( new byte[] {0xAA ,0xAA ,0xA0 }, // bit values 20 ) // length 20 bits };
如果不确定如何初始化某个字段,可以在模式中声明一个值,然后查看生成的 Values.cs 文件。
此示例初始化位串序列:
ASN.1 | C# |
---|---|
-- define seqOfbstr SEQUENCE OF BIT STRING . . . -- declare a value seqOfbstr { '11110000'B, '00001111'B } |
// create an instance of seqOfbstr var SeqOfbstr = new List |
您可以使用 -genSchemaInfo 编译器选项添加其他 ASN.1 架构信息(例如确切的架构名称、可选性等)。 指定此选项时,编译器会从两个命名空间添加 .NET 属性:本机 (System.Runtime.Serialization) 和 OSS 特定 ( Oss.Asn1Schema)。 有关 C# 属性的详细信息,请参阅 .NET 文档。
当指定 -genschemainfo 编译器选项时,ASN.1 C# 编译器会生成以下属性:
属性 | 描述 |
---|---|
[DataContract] |
为表示 SET、SEQUENCE、CHOICE 或 ENUMERATED 的每个类生成,其中:
|
[CollectionDataContract] |
为表示 SET OF 或 SEQUENCE OF 的每个类生成,其中:
|
[DataMember] |
为表示 SET、SEQUENCE 或 CHOICE 字段的每个 C# 属性生成,其中:
|
[EnumMember] |
为代表 ENUMERATED、命名数字或命名位的 C# 枚举的每个成员生成,其中:
|
具有开放类型的 PDU 包含可以在运行时识别的类型的字段,通常通过特殊的“id”字段和/或 ASN.1 约束。 开放类型可以独立于其包含(外部)PDU 进行编码和解码,并由包装在 oss.asn1.OpenType 类中的内部 PDU 对象表示。 oss.asn1.OpenType 类包含编码(字节数组)或解码形式(常规 PDU 类)的内部 PDU。 因此,开放类型字段可以在对“外部”PDU 执行的单个编码/解码操作中自动进行编码/解码,或者通过单独的编码/解码操作手动进行编码/解码 在“内部”PDU 上执行。
以下是手动和自动编码和解码的示例。 在这些示例中,outerPdu.Id 用于类型识别,其值在模式中预定义 (MyModule.Values.MyId1) :
您可以在单个编码操作中对开放类型和包含 PDU 进行编码。
outerPdu.Ot = new OpenType(new MyModule.MyOt1() { . . . }); // Set OT outerPdu.Id = MyModule.Values.MyId1.Id; // Set the Id for OT codec.Encode(outerPdu, data);
您可以单独对 innerPdu 进行预编码,也可以使用现有的(在别处编码的)原始字节:
// type/id defined in the schema as MyOt1/MyId1 var innerPdu = new MyModule.MyOt1() { . . . }; byte[] buffer = codec.Encode(innerPdu); // pre-encode OT . . . outerPdu.Ot = new OpenType(buffer); // Set encoded OT outerPdu.Id = MyModule.Values.MyId1.Id; // Set the Id
or
// "unknown" type, not defined in the schema outerPdu.Ot = new OpenType(new byte[] { . . . }); // raw bytes encoded OT outerPdu.Id = new ObjectIdentifier("1 2 3 4 . . ."); // Set the Id
然后
codec.Encode(outerPdu, data);
解码时,解码器必须知道是解码包含 PDU 的开放类型字段(自动)还是保持字段完整(手动)。 这由 AutoDecode 选项指示,应用程序可以将其设置为 true 或 false。
解码器使用查找表解析类型,也称为组件关系约束,由信息对象集在模式中定义。 未解析的类型(表中不匹配)导致开放类型字段保持编码形式,如果类型已知,以后仍可以手动解码。
codec.DecoderOptions.AutoDecode = true; . . . // decode both PDUs, outer and inner codec.Decode(data, outerPdu); . . . if (outerPdu.Ot.Decoded == null) { // dump bytes in HEX ValueNotationFormatter.Print(outerPdu.Ot.Encoded, outerPdu.Ot.Encoded.Length); throw new Exception("received unknown open type"); } // use inner PDU if (outerPdu.Id == MyModule.Values.MyId1.Id) // type MyOt1 innerPdu = (MyModule.MyOt1) outerPdu.Ot.Decoded; else if (outerPdu.Id == MyModule.Values.MyId2.Id) // type MyOt2 innerPdu = (MyModule.MyOt2) outerPdu.Ot.Decoded;
在一些复杂的情况下,无法自动解码开放类型,因为解码器无法识别类型。 这些情况如下:
在所有情况下,在自动解码包含一个或多个开放类型的 PDU 期间,任何开放类型字段都可以以其编码形式返回给应用程序(请参阅 OpenType.Encoded 和 OpenType.Decoded,其中 Decoded 成员将设置为 null),因此应用程序可以通过显式提供其类型来解码字段,例如 codec.Decode <MyModule.MyOt> (outerPdu.Ot)。
当组件关系约束没有给出类型信息但应用程序仍然知道类型时,或者当开放类型的编码/解码是可选和/或延迟时,手动解码很有用。
codec.DecoderOptions.AutoDecode = false; . . . // decode outer PDU, leave inner PDU in the encoded form codec.Decode(data, outerPdu); . . . // deferred decode of the inner PDU if (outerPdu.Id == MyModule.Values.MyId1.Id) innerPdu = codec.Decode<MyModule.MyOt1>(outerPdu.Ot); else if (outerPdu.Id == new ObjectIdentifier("1 2 3 4 14")) innerPdu = codec.Decode>MyModule.MyOtherOt>(outerPdu.Ot); else // unexpected/unknown type, dump bytes in HEX ValueNotationFormatter.Print(outerPdu.Ot.Encoded, outerPdu.Ot.Encoded.Length);
在编码、解码或 API 提供的任何其他功能(例如格式化打印)期间发生的所有错误都会通过异常报告给应用程序。 编解码器库仅抛出少数异常; 它们在 Oss.Asn1 命名空间下定义。
Asn1InvalidDataException 和 Asn1InvalidEncodingException 表示正在编码或解码的数据存在问题。
Asn1OutputFullException 表示与预分配的输出缓冲区大小有关的问题。
exception.Message 提供导致异常的错误的更详细描述,并且在适用时还包括负责的 PDU 字段和类型。 对于在编解码器库之外(例如,由 .NET 框架)引发的异常也是如此,因此大大简化了调试或故障排除。
通常,应用程序可以通过两种方式处理编码/解码异常:
byte[] buffer = new byte[10]; // small, but probably fits all the values bool encoded = false; while (!encoded) { try { codec.Encode(pdu, buffer); encoded = true; } catch (Asn1OutputFullException e) { // Oh, bigger than we thought? Well, we know how to handle it. buffer = new byte[buffer.Length * 2]; } }
using (StreamWriter logFile = new StreamWriter("log.txt")) { try { // encode/decode . . . } catch (Exception e) { logFile.WriteLine(e.ToString()); } }
以下限制适用于 E-XER 编码器/解码器:
部分解码功能可以对特定字段进行解码 输入消息,而其余字段被忽略。 使用部分 解码功能, 你可以
Codec 对象的 DecodePartial() 方法不返回解码的 PDU 值。 相反,它在以下情况下调用用户定义的回调方法 解码由 OSS.DataCallback 或 OSS.InfoCallback 编译器指令标记的每个字段,并可选择将解码后的字段值传递给 它。 您的回调方法可以分析解码的值,并通过设置 返回码,指示解码器终止或继续 解码。
只有 BER、DER、PER、UPER、CPER、CUPER、OER 和 COER 二进制编码 规则支持部分解码。
对于以下 ASN.1 语法,将提取嵌套在 homeAddress 中的邮政编码,而忽略嵌套在公司地址中的邮政编码:
M DEFINITIONS AUTOMATIC TAGS ::= BEGIN Subscriber ::= SEQUENCE { name VisibleString, company Company, homeAddress Address } Company ::= SEQUENCE { name VisibleString, address Address } Address ::= SEQUENCE { zipcode INTEGER( 0..99999 ), addressline VisibleString( SIZE (1..64) ) } END
首先,应用以下指令并编译规范 -enablePartialDecoding 或 -partialDecodeOnly 选项。
--<OSS.DataCallback M.Address.zipcode "MyZipcode">-- --<OSS.InfoCallback M.Subscriber.homeAddress "HomeAddressField">--
这些选项指示编译器生成具有以下回调方法声明的 PartialContentHandler 接口:
Oss.Asn1.ContentHandlerResponse BeginMyZipcode(); Oss.Asn1.ContentHandlerResponse EndMyZipcode(int Value); Oss.Asn1.ContentHandlerResponse BeginHomeAddressField(); Oss.Asn1.ContentHandlerResponse EndHomeAddressField();
Oss.Asn1.ContentHandlerResponse C# 枚举类型定义了可能的返回 回调方法中的代码:
然后,定义Oss.Asn1.ContentHandler的泛型子类和生成的PartialContentHandler接口,实现方法如下:
class MyPartialContentHandler<T> : Oss.Asn1.ContentHandler<T>, PartialContentHandler where T : Oss.Asn1.BasePdu, new() { private bool InsideHomeAddress = false; public Oss.Asn1.ContentHandlerResponse BeginMyZipcode() { return InsideHomeAddress ? Oss.Asn1.ContentHandlerResponse.Continue : Oss.Asn1.ContentHandlerResponse.Skip; } public Oss.Asn1.ContentHandlerResponse EndMyZipcode(int Value) { // Consume ZIP code // ... return Oss.Asn1.ContentHandlerResponse.Continue; } public Oss.Asn1.ContentHandlerResponse BeginHomeAddressField() { InsideHomeAddress = true; return Oss.Asn1.ContentHandlerResponse.Skip; } public Oss.Asn1.ContentHandlerResponse EndHomeAddressField() { return Oss.Asn1.ContentHandlerResponse.Skip; } }
最后,调用部分解码操作如下:
codec.DecodePartial(source, new MyPartialContentHandler<Subscriber>());
或者,您可以应用 OSS.InfoCallback to Subscriber.company 而不是 Subscriber.homeAddress:
--<OSS.InfoCallback M.Subscriber.company "Company">--
定义内容处理程序如下:
class MyPartialContentHandler<T> : Oss.Asn1.ContentHandler<T>, PartialContentHandler where T : Oss.Asn1.BasePdu, new() { public Oss.Asn1.ContentHandlerResponse BeginMyZipcode() { return Oss.Asn1.ContentHandlerResponse.Continue; } public Oss.Asn1.ContentHandlerResponse EndMyZipcode(int Value) { // Consume ZIP code // ... return Oss.Asn1.ContentHandlerResponse.Continue; } public Oss.Asn1.ContentHandlerResponse BeginCompany() { return Oss.Asn1.ContentHandlerResponse.Skip; } public Oss.Asn1.ContentHandlerResponse EndCompany() { return Oss.Asn1.ContentHandlerResponse.Continue; } }
您会得到相同的结果: homeAddress 邮政编码被提取并 公司地址 邮政编码被忽略。
注意:部分解码是非评估许可证中的一项收费功能。 联系销售人员以获取定价信息。
本文档适用于 C# 版本 5.0 及更高版本的 OSS® ASN.1 工具。
版权所有 © 2021 OSS Nokalva, Inc. 保留所有权利。
未经 OSS Nokalva, Inc. 事先许可,不得以任何形式或通过任何电子、机械、影印、记录或其他方式复制、存储在检索系统中或传输本出版物的任何部分。
OSS® ASN.1 Tools for C# 的每个分发副本都与特定许可证和相关的唯一许可证号相关联。 the OSS ASN.1 Tools for C# are available to you.