将架构编译成一组 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.