TOP

Using the API

Applies to: ASN.1/C# v5.3

Intuitive API

Once your schema is compiled into a set of C# files, add a few lines of code to your main() function and you can build your encoding application. The build must reference the encoder/decoder runtime library (asn1csrt.dll), which the generated code depends on.

ASN.1 API

ASN.1 API

The following example contains an application which assumes the MySch.asn schema file that includes a MyMod module with a MyValue value:

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);
        }
    }
}

For the most part, the API manipulates two kinds of ASN.1 objects: codecs and PDUs (data).

Namespaces

The OSS ASN.1 Tools for C# API consists of two types of namespaces:

Schema-specific
A set of compiler generated classes for your particular schema, that is, PDUs and values (if any are defined in the schema), including their time-optimized coding routines (MySchema.MyModule, for example).
Oss.Asn1
A set of classes that is independent of a particular schema and implements the generic behavior of ASN.1 types (object identifiers, bit strings), exceptions, primitive types, encoding/decoding routines, and utilities.

Oss.Asn1 classes are implemented as .NET runtime library (an assembly), which your application must include as a reference in order to be successfully built.

Byte Arrays and Streams

The previous example shows you how to encode a value into a buffer; however, you can also encode into a BER file using a System.IO stream and file:

using (Stream berfile = File.Create("PduEncoding.ber"))
     codec.Encode(pdu, berfile);

Data Objects

The representation names match the schema names, and the types follow the ASN.1 types, therefore you can easily access generated data. Primitive types are represented by int, bool, and string, while constructed types (SEQUENCE, CHOICE, ENUMERATED, BIT STRING, etc.) are represented by a wrapper class. For example:

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     
. . . 

Schema fields marked as DEFAULT or OPTIONAL are represented as nullable types (native .NET types can also be nullable, for example, int?, bool? etc). You are not required to transmit optional or default values, therefore you can omit these fields during encoding/decoding (by setting them to null). Here is an example of a schema and an application using such fields:

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; }
  	. . . .
}

The application can encode/decode by comparing or using the default value of the field:

// 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

NOTE: For performance reasons, codecs do not compare DEFAULT values to decide whether to encode them. It is the application's responsibility to omit these values during encoding (set them to null) so that they are not transmitted.

Using Class Initializers

You can set data during object construction using C# initializer syntax, as illustrated in the following example, which initializes the BIT STRING member of the CHOICE element shown above:

var bits = new MyPdu.ChType() 
{ 
   C = new Oss.Asn1.BitString ( 
       new byte[] {0xAA ,0xAA ,0xA0 }, // bit values
       20 )                            // length 20 bits
};

If you are unsure about how to initialize a certain field, you can declare a value in the schema and then look in the generated Values.cs file.

This example initializes a SEQUENCE OF BIT STRINGs:

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

Schema Information

You can add additional ASN.1 schema information (such as exact schema names, optionality, etc.) by using the -genSchemaInfo compiler option. When this option is specified, the compiler adds .NET attributes from both namespaces: native (System.Runtime.Serialization) and OSS-specific (Oss.Asn1Schema). For more information about C# attributes, see the .NET documentation.

The ASN.1 C# compiler generates the following attributes when the -genschemainfo compiler option is specified:

Attribute Description
[DataContract]
Generated for every class that represents SET, SEQUENCE, CHOICE, or ENUMERATED, where:
  • Name - specifies the schema name, if any, of the type.
  • Namespace - specifies the name of the schema module where the corresponding type is defined (omitted if Name is not present).
[CollectionDataContract]
Generated for every class that represents SET OF or SEQUENCE OF, where:
  • Name - specifies the ASN.1 name, if any, of the type.
  • Namespace - specifies the ASN.1 name of the module where the corresponding type is defined (omitted if Name is not present).
  • ItemName - specifies the ASN.1 name, if any, of the element.
[DataMember]
Generated for every C# property that represents a field of SET, SEQUENCE, or CHOICE, where:
  • Name - specifies the schema name, if any, of the field.
  • IsRequred - set to false if the field is OPTIONAL or has a DEFAULT value; otherwise it is set to true. The property is omitted on extension fields.
  • ItemName - specifies the ASN.1 name, if any, of the element.
[EnumMember]
Generated for every member of C# enum that represents ENUMERATED, named numbers, or named bits, where:
  • Value - specifies the schema name of the enumerator.

Open Types

A PDU with an open type contains a field of a type that can be identified at runtime, usually by a special "id" field and/or by an ASN.1 constraint. The open type can be encoded and decoded independently of its containing (outer) PDU and is represented by an inner PDU object wrapped into an oss.asn1.OpenType class. The oss.asn1.OpenType class contains the inner PDU in either encoded (a byte array) or decoded form (a regular PDU class). Therefore the open type field can be encoded/decoded either automatically in a single encode/decode operation performed on the "outer" PDU, or manually by a separate encode/decode operation performed on the "inner" PDU.

Below are examples of manual and automatic encoding and decoding. In these examples, outerPdu.Id is used for type identification with its values predefined in the schema (MyModule.Values.MyId1):

Automatic Encoding

You can encode an open type along with the containing PDU in a single encode operation.

outerPdu.Ot = new OpenType(new MyModule.MyOt1() { . . . }); // Set OT
outerPdu.Id = MyModule.Values.MyId1.Id; // Set the Id for OT	
       codec.Encode(outerPdu, data);
Manual Encoding

You can pre-encode the innerPdu separately or you can use existing (encoded elsewhere) raw bytes:

 // 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

then

codec.Encode(outerPdu, data);

When decoding, the decoder must know whether to decode the open type field along with the containing PDU (automatic) or to leave the field intact (manual). This is indicated by the AutoDecode option, which the application can set to true or false.

Automatic Decoding

The decoder resolves the type using a lookup table, also known as a component relation constraint, defined in the schema by the information object set. Unresolved types (no match in the table) cause the open type field to remain in the encoded form, which can still be manually decoded later, provided the type is known.

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;

In some complex cases, auto decoding of an open type is not possible, because the decoder is unable to identify the type. These cases are as follows:

  • The open type is constrained by a component relation constraint, but the bytes in the encoding do not provide unique identification (the corresponding information object set does not contain a matching entry or contains multiple matching entries). The following cannot be automatically decoded at this time, but may be in the future.
  • The open type is constrained by a component relation constraint, but the type's encoding bytes come before its identification bytes in the encoding stream.
  • The open type is constrained by a component relation constraint, but is nested inside a SET.
  • The open type is constrained by a component relation constraint, but is nested inside a CONTAINING constraint while identified by a field outside of it.
  • The open type is not constrained by a component relation constraint, but is constrained by a simple ASN.1 type constraint.

In all cases, during automatic decoding of a PDU that contains one or more open types, any open type field can be returned to the application in its encoded form (see OpenType.Encoded and OpenType.Decoded, where the Decoded member will be set to null), so the application can decode the field by providing its type explicitly, for example, codec.Decode <MyModule.MyOt> (outerPdu.Ot).

Manual Decoding

Manual decoding is useful when no type information is given by the component relation constraint, but the application still knows the type, or when encoding/decoding of the open type is optional and/or deferred.

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);

Error Reporting/Exceptions

All errors that occur during encoding, decoding, or any other functionality provided by the API, such as formatted printing, are reported to the application by exceptions. There are only a few exceptions that are thrown by the codec library; they are defined under the Oss.Asn1 namespace.

Asn1InvalidDataException and Asn1InvalidEncodingException indicate a problem with the data being encoded or decoded.

Asn1OutputFullException indicates a problem related to the size of the preallocated output buffer.

exception.Message provides a more detailed description of the error that caused the exception and, when applicable, also includes the responsible PDU field and type. This is also true for exceptions thrown outside the codec library (for example, by the .NET framework), so debugging or troubleshooting is greatly simplified.

Generally, there are two ways an application can handle encoding/decoding exceptions:

  • Handle expected exceptions: catch a specific exception type that the application expects and knows how to handle, for example:
    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];
               }
           }
  • Troubleshoot unexpected exceptions: catch all exceptions while debugging or troubleshoot at a later time by logging. Note that ToString() includes exception details, such as stack trace, etc.
    using (StreamWriter logFile = new StreamWriter("log.txt"))
    {
        try
        {
            // encode/decode
    		. . .
        }
        catch (Exception e)
        {
            logFile.WriteLine(e.ToString());
        }
    }

Encoder/Decoder Limitations

The following limitations apply to the E-XER encoder/decoder:

  1. The encoder does not enforce namespace constraints for ANY-ELEMENT and ANY-ATTRIBUTE fields.
  2. The encoder does not ensure that attributes specified by the ANY-ATTRIBUTES field have unique names.
  3. When it encounters the XMLIdentifierList alternative of a BIT STRING encoding, the decoder throws an exception.
  4. The compiler may fail to detect an invalid use of the DEFAULT-FOR-EMPTY encoding instruction. In this case, the encoder may generate an invalid encoding that does not conform to the X.693 requirements.
  5. The decoder incorrectly decodes the value of a SEQUENCE that has the DEFAULT-FOR-EMPTY and the USE-NIL encoding instructions when the value of the nil attribute is false or the nil attribute is omitted.
  6. An extension field with the UNTAGGED encoding instruction may be decoded incorrectly.
  7. A value of a SET may be decoded incorrectly when one of its fields has the ANY-ELEMENT encoding instruction applied.
  8. When decoding from a Stream or TextReader object, the XML conformance level is always set to fragment. If the document conformance level is preferable, XmlReader should be used as the input source.
  9. When decoding from a Stream or TextReader object, the decoder employs internal buffering. For this reason, the input Stream or the TextReader object should not contain any data other than concatenated XML fragments.
  10. The following ASN.1 types are currently not supported by CPER: SET OF, GeneralString, and GraphicString. The runtime throws an exception on an attempt to encode or decode a value of these types.

Partial Decoding

The partial decoding feature enables decoding of particular fields from input messages while the remaining fields are ignored. Using the partial decoding feature, you can

  • Extract data without writing code to access deeply nested fields of complex PDUs.
  • Reduce the memory footprint of applications that contain minimal information in their PDUs.

The DecodePartial() method of a Codec object does not return the decoded PDU value. Instead, it invokes user-defined callback methods when decoding each field marked by the OSS.DataCallback or OSS.InfoCallback compiler directive and, optionally, passes the decoded field value to it. Your callback method can analyze the decoded value, and by setting the return code, instructs the decoder to either terminate or continue decoding.

Remarks

Only the BER, DER, PER, UPER, CPER, CUPER, OER, and COER binary encoding rules support partial decoding.

Example

For the following ASN.1 syntax, the ZIP code nested within homeAddress will be extracted while the ZIP code nested within the company address is ignored:

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

First, apply the following directives and compile the specification with either the -enablePartialDecoding or -partialDecodeOnly option.

--<OSS.DataCallback M.Address.zipcode "MyZipcode">--
--<OSS.InfoCallback M.Subscriber.homeAddress "HomeAddressField">--

The options instruct the compiler to generate the PartialContentHandler interface with the following callback method declarations:

Oss.Asn1.ContentHandlerResponse BeginMyZipcode();
Oss.Asn1.ContentHandlerResponse EndMyZipcode(int Value);
Oss.Asn1.ContentHandlerResponse BeginHomeAddressField();
Oss.Asn1.ContentHandlerResponse EndHomeAddressField();

The Oss.Asn1.ContentHandlerResponse C# enum type defines possible return codes from the callback methods:

  • Continue - Continue with normal processing.
  • Skip - If returned from a "Begin" callback, skip to the end of the field, suppressing further callbacks that could occur inside the field, including the "End" field callback. If returned from an "End" callback, skip to the end of the PDU, consuming all the encoded stream data without any further callback invocations.
  • Abort - Terminate partial decode procedure and return to the caller immediately.

Then, define the generic subclass of Oss.Asn1.ContentHandler and the generated PartialContentHandler interface and implement the methods as follows:

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.Continue;
  }
  public Oss.Asn1.ContentHandlerResponse EndHomeAddressField()
  {
    return Oss.Asn1.ContentHandlerResponse.Skip;
  }
}

Finally, invoke the partial decode operation as follows:

codec.DecodePartial(source, new MyPartialContentHandler<Subscriber>());

Alternatively, you can apply OSS.InfoCallback to Subscriber.company rather than Subscriber.homeAddress:

--<OSS.InfoCallback M.Subscriber.company "Company">--

Define the content handler as follows:

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;
  }
}

You would get the same result: the homeAddress ZIP code is extracted and the company address ZIP code is ignored.

See Also

Note: Partial decoding is a chargeable feature in non-evaluation licenses. Contact Sales to obtain pricing information.

Value Parsing

The ASN.1/C# Tools can parse ASN.1 value notation data into C# objects that represent values defined by value notation.

To parse values specified in ASN.1 value notation format:

  1. Instantiate the appropriate parser object:
    • ValueNotationParser - This parser can parse a single value contained in a string.
    • ValueNotationReader - This parser can parse one or more values.
    When you specify the -avn compiler option, the asn1cs compiler generates the corresponding C# classes in the ValueNotationParser.cs source file.
  2. Invoke the ParseValue() or ParseValueAssignment() method of the ValueNotationParser class or the ReadValue() or ReadValueAssignment() method of the ValueNotationReader class to parse the value.

Example

The Bcas.asn schema file is used in this example.

BCASModule DEFINITIONS ::= BEGIN

BBCard ::= SEQUENCE {
    name IA5String (SIZE (1..60)),
    team IA5String (SIZE (1..60)),
    age INTEGER (1..100),
    position IA5String (SIZE (1..60)),
    handedness ENUMERATED
        {left-handed(0), right-handed(1), ambidextrous(2)},
    batting-average REAL
}

END

The following source code shows the sample C# application that parses the BBCard ASN.1 type values:

namespace SimpleApp
{
    class Program
    {
        static void Main()
        {
            // The value notation for the value of the BBCard type
            string myCard =
            @"{
                name ""Casey"",
                team ""Mudville Nine"",
                age 32,
                position ""left field"",
                handedness ambidextrous,
                batting-average {mantissa 250, base 10, exponent -3}
            }";

            // Instantiate the ValueNotationParser
            var parser = new Bcas.ValueNotationParser();

            // Parse the value notation contained in the myCard string
            var myCardPdu1 = parser.ParseValue(myCard, 
                new Bcas.BCASModule.BBCard());

            // Print the C# object parsed from the value notation in the
            // myCard string
            System.Console.WriteLine("myCardPdu1 " + myCardPdu1);

            // Create the string containing the ASN.1 value assignment for the
            // value of BBCard
            string myCardAssignment = "myCard BBCard ::= " + myCard;

            // Parse the value assignment contained in the myCardAssignment
            // string. The parser will detect the type of the value from the
            // ASN.1 type name specified in the value assignment (BBCard).
            var myCardPdu2 = parser.ParseValueAssignment(myCardAssignment);

            // Print the C# object parsed from the value assignment in the
            // myCardAssignment string
            System.Console.WriteLine("myCardPdu2 " + myCardPdu2);

            // Parse the value assignment contained in the myCardAssignment
            // string. The type of the value is specified explicitly by the 
            // empty instance of the BBCard class.
            var myCardPdu3 = parser.ParseValueAssignment(myCardAssignment, 
                new Bcas.BCASModule.BBCard());

            // Print the parsed C# object.
            System.Console.WriteLine("myCardPdu3 " + myCardPdu3);

            // Parse the input containing multiple values of the BBCard type
            string myCards =
            @"-- first value
            {
                name ""Casey"",
                team ""Mudville Nine"",
                age 32,
                position ""left field"",
                handedness ambidextrous,
                batting-average {mantissa 250, base 10, exponent -3}
            }
            -- second value
            {
                name ""Doug"",
                team ""Octopus Eight"",
                age 27,
                position ""right field"",
                handedness right-handed,
                batting-average {mantissa 250, base 10, exponent -3}
            }";
            // Instantiate the ValueNotationReader
            using (var reader = new Bcas.ValueNotationReader(
                new System.IO.StringReader(myCards)))
            {
                Oss.Asn1.BasePdu myCardPdu;
                // Parse all the values contained in the myCards
                while ((myCardPdu = reader.ReadValue(new Bcas.BCASModule.BBCard())) != null)
                    System.Console.WriteLine("myCardPdu " + myCardPdu);
            }
        }
    }
}

Remarks

The following methods automatically determine the value type from the ASN.1 type name specified in the ASN.1 value assignment:

  • BasePdu ParseValueAssignment(string);
  • BasePdu ReadValueAssignment();

The following methods allow you to specify the value type explicitly. The value type is identified by the "BasePdu" parameter, which is an empty instance of the C# class that represents the value type:

  • BasePdu ParseValueAssignment(string, BasePdu);
  • BasePdu ReadValueAssignment(BasePdu);

Implementation restrictions

  • Values of arcs present in OBJECT IDENTIFIER and RELATIVE-OID type values are limited to a range of 64-bit signed numbers.
  • In the notation that is used for open type values
    Type ":" Value
    only the "typereference" alternative of the "Type" production is supported. Also, the type specified by "typereference" must be a PDU.
  • ASN.1:1990 schemas that include a SEQUENCE, SET, or CHOICE type definition from which component names are omitted are not supported.

NOTE: The ASN.1 value notation parser generated by the -avn option is a chargeable feature in non-evaluation licenses. Contact Sales to obtain pricing information.

See Also

-avn

Constraints with Bounds Information

The -genSizeValueRangeAttributes option instructs the ASN.1 compiler to generate custom SizeAttribute and ValueRangeAttribute attributes from the Oss.Asn1 namespace. The attributes include the effective minimum and maximum values of permitted counts, lengths, and integer values that are computed based on the subtype constraints present in the input ASN.1 schema.

SizeAttribute is generated for the following types that have effective size constraints:

  • SEQUENCE OF
  • SET OF
  • restricted character string
  • BIT STRING
  • OCTET STRING

ValueRangeAttribute is generated for INTEGER types that have effective value range constraints and are not marked with the HUGE directive.

The SizeAttribute and ValueRangeAttribute attributes are attached to C# properties that represent fields associated with ASN.1 types that have size or value range constraints. Subtype constraints applied to elements of SEQUENCE OF and SET OF types are mapped to the attributes attached to the corresponding parent collection class.

Attribute Description
SizeAttribute
Generated for:
  • a C# property that represents a SET, SEQUENCE, or CHOICE type field when the ASN.1type of the field has an effective size constraint.
  • a C# Value property of the class that represents a schema PDU type that has an effective size constraint.
  • a C# collection class that represents a SEQUENCE OF or SET OF type that contains an element whose ASN.1 type has an effective size constraint.
The attribute has three properties, as follows:
  • Min ? specifies an effective minimum value for component counts or string lengths.
  • Max - specifies an effective maximum value for component counts or string lengths.
  • Is extensible ? returns "true" when the corresponding size constraint is extensible, for example, IA5String (SIZE(1..5,...)).
The C# "int" type of the Min and Max properties has values between "0" and INT_MAX (2147483647).
ValueRangeAttribute
Generated for:
  • a C# property that represents a SET, SEQUENCE, or CHOICE type field that has an INTEGER type that is constrained with an effective value range and is not marked with the HUGE directive.
  • a C# Value property of the class that represents a schema PDU with an INTEGER type that has an effective value range and is not marked with the HUGE directive.
  • a C# collection class that represents a SEQUENCE OF or SET OF type with an INTEGER type element that is constrained with an effective value range and is not marked with the HUGE directive.
The attribute has four properties, as follows:
  • Min ? specifies an effective minimum value of an "object" type.
  • Max - specifies an effective maximum value of an "object" type.
  • OperandType - specifies the type of values stored in the Min and Max properties that can be either "int", "long", or "ulong". You would use a cast to access the actual maximum and minimum values, as follows:
    if (rangeAttr.OperandType == typeof(int))
          int min = (int)rangeAttr.Min; 
          int max = (int)rangeAttr.Max;
    } ...
  • Is extensible ? returns "true" when the corresponding value range constraint is extensible, for example: INTEGER (-10..10,...).
The Min and Max properties have a C# "object" type. The OperandType property is used to determine the exact integer type of the Min and Max properties.

Attributes can be retrieved by an application using C# Reflection.

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Oss.Asn1;
using Bcas;
using Bcas.BcasModule;

public static class TestCustomAttributes
{
    . . .
   public static int Main(string[] args)
   {
      try
      {
          PropertyInfo[] props = typeof(BBCard).GetProperties();
          foreach (PropertyInfo prop in props)
          {
             ProcessAttributes( prop, Attribute.GetCustomAttributes(prop) );

             // Custom attributes associated with Size and ValueRange constraints applied to
             // elements of SEQUENCE OF and SET OF types are attached to the parent collection
             // class as a target.
             Type propType = prop.PropertyType;
             if (propType.BaseType.Namespace == "System.Collections.Generic" &&
                             propType.GetProperty("Item") != null)
             {
                 // Get the property of the collection element.
                 PropertyInfo itemProp = propType.GetProperty("Item");
                 // Access the element's constraints from the attributes applied
                 // to the parent's collection class.
                 ProcessAttributes( itemProp, 
                                         Attribute.GetCustomAttributes(propType) );
                 }
             }
         }
         catch (Exception e)
         {
             Console.WriteLine("ERROR: " + e.Message + "\n");
         }
         return 0;
     }
}

Where the ProcessAttributes method may look like follows:

    . . .

    public static void ProcessAttributes (PropertyInfo prop, Attribute[] attributes)
    {
        Type propType = prop.PropertyType;

        foreach (Attribute attr in attributes)
        {
            if (attr.GetType() == typeof(SizeAttribute))
            {
                SizeAttribute szAttr = (SizeAttribute)attr;
                int szmin = szAttr.Min;
                int szmax = szAttr.Max;
	      . . .
            }
           else if (attr.GetType() == typeof(ValueRangeAttribute))
           {
                ValueRangeAttribute vrAttr = (ValueRangeAttribute)attr;
                if (vrAttr.OperandType == typeof(int))
                {
                    int vrmin = (int)vrAttr.Min;
                    int vrmax = (int)vrAttr.Max;
 		  . . .
                }
                else if (vrAttr.OperandType == typeof(long))
                {
                    long vrmin = (long)vrAttr.Min;
                    long vrmax = (long)vrAttr.Max;
		  . . .
                }
                else if (vrAttr.OperandType == typeof(ulong))
                {
                    ulong vrmin = (ulong)vrAttr.Min;
                    ulong vrmax = (ulong)vrAttr.Max;
		  . . .
                }
            }
        }
    }

This documentation applies to the OSS® ASN.1 Tools for C# release 5.3 and later.

Copyright © 2024 OSS Nokalva, Inc. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any means electronic, mechanical, photocopying, recording or otherwise, without the prior permission of OSS Nokalva, Inc.
Every distributed copy of the OSS® ASN.1 Tools for C# is associated with a specific license and related unique license number. That license determines, among other things, what functions of the OSS ASN.1 Tools for C# are available to you.