使用API

应用至: ASN.1/C# 5.0

直观的 API

将架构编译成一组 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 包含两种类型的命名空间:

模式专用
一组编译器为您的特定模式生成的类,即 PDU 和值(如果在模式中定义),包括它们的时间优化编码例程(MySchema.MyModule , 例如)。
Oss.Asn1
一组独立于特定模式并实现 ASN.1 类型(对象标识符、位字符串)、异常、原始类型、编码/解码例程和实用程序的通用行为的类。

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() 
{
  new Oss.Asn1.BitString (
        new byte[] {0xF0},8), 
  new Oss.Asn1.BitString (
        new byte[] {0x0F},8)
}

架构信息

您可以使用 -genSchemaInfo 编译器选项添加其他 ASN.1 架构信息(例如确切的架构名称、可选性等)。 指定此选项时,编译器会从两个命名空间添加 .NET 属性:本机 (System.Runtime.Serialization) 和 OSS 特定 ( Oss.Asn1Schema)。 有关 C# 属性的详细信息,请参阅 .NET 文档。

当指定 -genschemainfo 编译器选项时,ASN.1 C# 编译器会生成以下属性:

属性 描述
[DataContract]
为表示 SET、SEQUENCE、CHOICE 或 ENUMERATED 的每个类生成,其中:
  • Name - 指定类型的模式名称(如果有)。
  • Namespace - 指定定义相应类型的模式模块的名称(如果 Name 不存在,则省略)。
[CollectionDataContract]
为表示 SET OF 或 SEQUENCE OF 的每个类生成,其中:
  • Name - 指定类型的 ASN.1 名称(如果有)。
  • Namespace - 指定定义相应类型的模块的 ASN.1 名称(如果 Name 不存在则省略)。
  • ItemName - 指定元素的 ASN.1 名称(如果有)。
[DataMember]
为表示 SET、SEQUENCE 或 CHOICE 字段的每个 C# 属性生成,其中:
  • Name - 指定字段的架构名称(如果有)。
  • IsRequred - 如果该字段是 OPTIONAL 或具有 DEFAULT 值,则设置为 false; 否则设置为真。 该属性在扩展字段中被省略。
  • ItemName - 指定元素的 ASN.1 名称(如果有)。
[EnumMember]
为代表 ENUMERATED、命名数字或命名位的 C# 枚举的每个成员生成,其中:
  • Value - 指定枚举器的模式名称。

打开类型

具有开放类型的 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;

在一些复杂的情况下,无法自动解码开放类型,因为解码器无法识别类型。 这些情况如下:

  • 开放类型受组件关系约束,但编码中的字节不提供唯一标识(对应的信息对象集不包含匹配条目或包含多个匹配条目)。 目前无法自动解码以下内容,但将来可能会。
  • 开放类型受组件关系约束的约束,但该类型的编码字节在编码流中位于其标识字节之前。
  • 开放类型受组件关系约束,但嵌套在 SET 内。
  • 开放类型受组件关系约束的约束,但嵌套在 CONTAINING 约束内,同时由其外部的字段标识。
  • 开放类型不受组件关系约束,但受简单 ASN.1 类型约束。

在所有情况下,在自动解码包含一个或多个开放类型的 PDU 期间,任何开放类型字段都可以以其编码形式返回给应用程序(请参阅 OpenType.EncodedOpenType.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];
               }
           }
  • 排除意外异常:在调试时捕获所有异常,或稍后通过记录进行故障排除。 请注意,ToString() 包含异常详细信息,例如堆栈跟踪等。
    using (StreamWriter logFile = new StreamWriter("log.txt"))
    {
        try
        {
            // encode/decode
    		. . .
        }
        catch (Exception e)
        {
            logFile.WriteLine(e.ToString());
        }
    }

编码器/解码器限制

以下限制适用于 E-XER 编码器/解码器:

  1. 编码器不强制 ANY-ELEMENT 和 ANY-ATTRIBUTE 字段的命名空间约束。
  2. 编码器不确保由 ANY-ATTRIBUTES 字段指定的属性具有唯一名称。
  3. 当它遇到 BIT STRING 编码的 XMLIdentifierList 替代方案时,解码器会引发异常。
  4. 编译器可能无法检测到 DEFAULT-FOR-EMPTY 编码指令的无效使用。 在这种情况下,编码器可能会生成不符合 X.693 要求的无效编码。
  5. 解码器错误地解码具有 DEFAULT-FOR-EMPTY 和 USE-NIL 编码指令当值为 nil 属性为 falsenil 属性被省略。
  6. 带有 UNTAGGED 编码指令的扩展字段可能被错误解码。
  7. 当 SET 的某个字段应用了 ANY-ELEMENT 编码指令时,可能会错误地解码 SET 的值。
  8. StreamTextReader 对象解码时,XML 一致性级别始终设置为 fragment 。 如果 document 一致性级别更可取,则应使用 XmlReader 作为输入源。
  9. StreamTextReader 对象解码时,解码器使用内部缓冲。 出于这个原因,输入 StreamTextReader 对象不应包含除了连接的 XML 片段之外的任何数据。
  10. CPER 目前不支持以下 ASN.1 类型:SET OF, 通用字符串和图形字符串。 运行时在 尝试对这些类型的值进行编码或解码。

部分解码

部分解码功能可以对特定字段进行解码 输入消息,而其余字段被忽略。 使用部分 解码功能, 你可以

  • 无需编写代码即可提取数据以访问深度嵌套的字段 复杂的 PDU。
  • 减少包含最少应用程序的内存占用 PDU 中的信息。

Codec 对象的 DecodePartial() 方法不返回解码的 PDU 值。 相反,它在以下情况下调用用户定义的回调方法 解码由 OSS.DataCallbackOSS.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# 枚举类型定义了可能的返回 回调方法中的代码:

  • 继续 - 继续正常处理。
  • 跳过 - 如果从 "Begin" 回调返回,则跳到 场,进一步压制 可能在字段内发生的回调,包括 "End" 字段回调。 如果从 "End" 回调返回,则跳到 PDU,消耗所有编码的 流数据,无需任何进一步的回调调用。
  • 终止 - 终止部分解码过程并立即返回给调用者。

然后,定义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.