ASN1(2)                                                   ASN1(2)

     NAME
          asn1: decode, encode - ASN.1 (X.208), BER (X.209) encoding

     SYNOPSIS
          include "asn1.m";
          asn1 := load ASN1 ASN1->PATH;
          asn1->init();

          Elem: adt {
              tag: Tag;
              val: ref Value;

              is_seq:         fn(e: self ref Elem) : (int, list of ref Elem);
              is_set:         fn(e: self ref Elem) : (int, list of ref Elem);
              is_int:         fn(e: self ref Elem) : (int, int);
              is_bigint:      fn(e: self ref Elem) : (int, array of byte);
              is_bitstring:   fn(e: self ref Elem) : (int, int, array of byte);
              is_octetstring: fn(e: self ref Elem) : (int, array of byte);
              is_oid:         fn(e: self ref Elem) : (int, ref Oid);
              is_string:      fn(e: self ref Elem) : (int, string);
              is_time:        fn(e: self ref Elem) : (int, string);
              tostring:       fn(e: self ref Elem) : string;
          };

          Tag: adt {
              class: int;
              num: int;
              constr: int;

              tostring: fn(t: self Tag) : string;
          };

          Value: adt {
              pick {
                  Bool or Int =>
                      v: int;
                  Octets or BigInt or Real or Other =>
                      bytes: array of byte;
                  BitString =>
                      unusedbits: int;
                      bits: array of byte;
                  Null or EOC =>
                      ;
                  ObjId =>
                      id: ref Oid;
                  String =>
                      s: string;
                  Seq or Set =>
                      l: list of ref Elem;
              }

     Page 1                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

              tostring: fn(v: self ref Value): string;
          };

          Oid: adt {
              nums: array of int;
              tostring: fn(o: self ref Oid): string;
          };

          init: fn();
          decode:       fn(a: array of byte) : (string, ref Elem);
          decode_seq:   fn(a: array of byte) : (string, list of ref Elem);
          decode_value: fn(a: array of byte, kind, constr: int):
                        (string, ref Value);
          encode:       fn(e: ref Elem) : (string, array of byte);
          oid_lookup:   fn(o: ref Oid, tab: array of Oid) : int;
          print_elem:   fn(e: ref Elem);

     DESCRIPTION
          The ASN1 limbo module supports decoding and encoding of the
          ASN.1 Basic Encoding Rules (BER, ITU-T Recommendation
          X.209).  Despite its name, the module is not a parser for
          Abstract Syntax Notation One (ASN.1, ITU-T Recommendation
          X.208).

          ASN1 handles the BER encodings of all types from the ASN.1
          Universal class, and provides a simple OBJECT IDENTIFIER
          comparison facility.

          For simplicity, ASN1 does not take a description of the
          ASN.1 module of the data being processed.  Consequently, the
          (de)composition of tagged types must be performed by the
          application.  ASN1 does not know the context of tagged val-
          ues and so cannot determine the underlying Universal type to
          be able to encode or decode the value automatically.  See
          the section on Tagging for details on how the application
          should handle both implicit and explicit tagging.

          init()
               The module must be initialised by calling this function
               before any other module functions or associated adt
               member functions are called.

          decode(a)
               Convert the BER encoding given by the byte array a into
               an Elem representing the ASN.1 value.  The byte array
               must contain the entire BER encoding of the value and
               any component values.

               Item values not tagged as a Universal type are con-
               verted to an Elem comprised of the decoded Tag and a
               value given by the Value.Octets variant, which contains

     Page 2                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

               the original encoding of the value stripped of the BER
               tag and length header.

               The function returns a tuple composed of an error
               string and the decoded Elem.  If no errors are encoun-
               tered the error string is nil.

          decode_seq(a)
               Like decode except that the data in a is the encoding
               of an item of type SEQUENCE, SEQUENCE OF, SET or SET OF
               which has been stripped of its tag and length header.
               The function decodes all of the items in the SEQUENCE
               or SET.

               The return value is a tuple composed of an error string
               and the list of Elems forming the SEQUENCE or SET.

          decode_value(a, kind, constr)
               Convert the encoding of a single item value to a Value
               data structure.

               The array a does not include the tag and length header.
               Instead, the value's Universal type is given by the
               kind argument and length is given by that of the array.
               The constr argument indicates if the encoding is in the
               BER constructed form or not.  A value of 0 indicates
               that the primitive encoding is used, all other values
               indicate the constructed encoding.

               The function returns a tuple composed of an error
               string and a Value reference.

          encode(e)
               Convert the Elem e to a BER encoding of the element.
               If the element is of a structured type, such as
               SEQUENCE or SET, then all component values are also
               exhaustively encoded.

               The encoding can fail if the Tag and Value of the ele-
               ment are not compatible.  The constr field of the Tag
               is currently ignored.

               The function returns a tuple comprising an error string
               and the BER encoding. If no errors are encountered the
               error string is nil and the second part of the returned
               tuple is a byte array of the BER encoding.

          oid_lookup(o, tab)
               Lookup an OBJECT IDENTIFIER value in an array of such
               values.  Returns the index of the first exact match of
               o in the tab array.  Returns -1 if no match is found.

     Page 3                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

          print_elem(e)
               Print a textual representation of the element to stan-
               dard output.  The output is that given by
               Elem.tostring(), followed by a newline character.

        Elem adt
          This is the principal data structure, representing the value
          of an ASN.1 item.  The adt couples a data representation,
          the Value, with its type specifier, the Tag.

          Elem.tag
               Specifies the ASN.1 type of the element value.  See the
               description of the Tag adt for more details.

          Elem.val
               The value of the element.  See the description of the
               Value adt for more details.

          All of the e.is_Type member functions test whether the spe-
          cific Value pick variant of Elem.val and the ASN.1 Universal
          type, given by the tag, match and are of the requested form.
          A successful match yields the type specific data from the
          Value pick variant.  The association of Universal types to
          Value pick variants is given in the section on the Value
          adt.

          The function e.is_int succeeds for BOOLEAN and INTEGER ASN.1
          types.  The function e.is_string succeeds for all of the
          ASN.1 Universal string types.

          Except for is_bitstring, each function returns a tuple of
          two values.  The first tuple item is an integer, 1 for suc-
          cess, 0 for failure.  The second item is the type specific
          data from the Value pick variant.

          e.is_bitstring()
               Like the is_Type functions described above.  Tests that
               the element is a BIT STRING and returns its data.

               The return value is a tuple comprised of two integers
               and an array of bytes.  The byte array represents the
               bit string.  The first integer is 1 for success, 0 for
               failure.  The second integer is the number of unused
               bits in the last byte of the data array.  See the
               description of the Value.BitString variant for more
               information.

          e.tostring()
               returns a textual representation of the element formed
               by joining the strings returned from e.tag.tostring()
               and e.val.tostring().

     Page 4                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

        Tag adt
          The Tag adt denotes the ASN.1 type of a Value instance.

          Tag.class
               Specifies the class of the type and can take one of the
               values: ASN1->Universal, ASN1->Application,
               ASN1->Context or ASN1->Private.

          Tag.num
               Identifies the particular type, or tag, within the
               specified class.  Tag numbers for the Universal class
               are given in the asn1.m header file.  The inconsistent
               use of upper-case and mixed-case identifiers comes
               straight from the ITU-T Recommendation.

          Tag.constr
               This flag is set by the ASN1 decode functions to mark
               if the BER constructed encoding was used for the value.
               A zero value indicates the BER primitive encoding,
               non-zero indicates the constructed encoding.

          t.tostring()
               Returns a string representation of the Tag.  For Uni-
               versal class tags the function returns the string
               ``UNIVERSAL Name'', where Name is the standard name of
               the specified Universal type.  For other classes the
               function returns the class name, in upper-case, fol-
               lowed by the tag number.

        Value adt
          This pick adt provides the representation for values of each
          of the various Universal class types.  Values of all other
          classes are represented by the Value.Octets branch of the
          pick.

          v.tostring()
               Returns a string representation of the Value.

          The following table lists each variant of the pick, indicat-
          ing the ASN.1 Universal type values it represents, followed
          by a brief description.  For each variant of the pick, v is
          taken to be of that particular type.

          Value.Bool
               BOOLEAN

               v.v equals zero for FALSE, non-zero values represent
               TRUE.

          Value.Int
               INTEGER, ENUMERATED

     Page 5                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

               The value is given by v.v

          Value.BigInt
               Used for INTEGER values too large to fit a Limbo int.

               The array v.bytes contains the encoding of the value.
               The array does not include the tag and length prefix.

          Value.Octets
               OCTET_STRING, ObjectDescriptor

               The octet string is given by the v.bytes array.

          Value.Null
               NULL

          Value.ObjId
               OBJECT_ID

               The OBJECT_ID value is represented by the Oid adt given
               by v.id.

          Value.Real
               REAL

               ASN1 does not convert the value into the Limbo real
               data type.  The encoding of the value is given by the
               v.bytes array, which does not include the tag and
               length prefix.

          Value.Other
               EXTERNAL, EMBEDDED_PDV and Unknown Universal types

               The raw bytes of the value, excluding the tag nad
               length header, are given by the v.bytes array.

          Value.BitString
               BIT_STRING

               The number of bits in the BIT_STRING value does not
               have to be a multiple of 8.  Bits are packed into bytes
               MSB first.  The bytes representing the BIT_STRING
               value, including the potentially incomplete last byte,
               are given by the v.bits array.  The number of unused
               bits in the last byte of the array is given by
               v.unused, counting from the LSB.

               The BER constructed encoding of values other than
               zero-length is not implemented.

          Value.EOC
               End of Contents octets marker.

     Page 6                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

               This value is not normally returned to the application;
               it is used privately by BER to support indefinite
               length value encodings.

          Value.String
               NumericString, PrintableString, TeletexString, Video-
               texString, IA5String, UTCTime, GeneralizedTime, Graph-
               icString, VisibleString, GeneralString, UniversalString
               or BMPString.

               The text is given by the v.s Limbo string.  Currently
               no character-set conversion is performed between the
               ASN.1 string byte codes and the Unicode code-points of
               the Limbo string.

          Value.Seq
               SEQUENCE, SEQUENCE OF

               ASN.1 assigns both constructs the same type tag.  The
               difference between them is that, within the ASN.1 nota-
               tion, the elements of a SEQUENCE OF structure are con-
               strained to be of the same type.  BER and, conse-
               quently, ASN1 do not directly enforce the restriction.

               The elements of the sequence are given by the v.l list.

          Value.Set
               SET, SET OF

               ASN.1 assigns both constructs the same type tag.  The
               difference between them is that, within the ASN.1 nota-
               tion, SET items are formed from an unordered list of
               distinct types, whereas SET OF items are formed from an
               unordered list of the same type.  BER and ASN1 do not
               enforce these constraints.

               The elements of the set are given by the v.l list.

        Oid adt
          The Oid adt provides the value representation for OBJECT
          IDENTIFERs.  Within the ASN.1 notation OBJECT IDENTIFIERs
          ultimately map to an ordered list of INTEGERs.

          Oid.nums
               The value of the OBJECT IDENTIFIER, given as an array
               of int.

          o.tostring()
               Returns a textual representation of the OBJECT IDENTI-
               FIER in the form of a `.' separated list of numbers.

        Tagging

     Page 7                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

          Tagging is an ASN.1 mechanism for disambiguating values.  It
          is usually applied to component types, where several compo-
          nents of a structured type have the same underlying Univer-
          sal class type.  Tagging allows the client application to
          determine to which item of the structured type a value
          instance belongs.

          There are two types of tagging, implicit and explicit,
          defining the manner in which the values are encoded.

          Implicitly tagged values are encoded in the same way as the
          underlying type, but with the tag class and number replaced
          by that specified.

          Explicitly tagged values are encoded in a nested fashion.
          The outermost item bears the specified tag and its contents
          is the full encoding of the original value using the tag of
          its underlying type.

          The following examples of how to decode and encode simple
          tagged types should make the distinction clear.

        Decoding Tagged Values
          Consider the following ASN.1 type definitions:

              Type1 ::= INTEGER
              Type2 ::= [Application 2] Type1     -- Explicitly tagged
              Type3 ::= [3] IMPLICIT Type1        -- Implicitly tagged

          For each of the types the value 16r55 will be decoded as
          follows:

               (error, elem) := asn1->decode(data);

          Type1 (primitive type)
               elem.tag.class == Universal
               elem.tag.num == INTEGER
               tagof elem.val == tagof Value.Int
               elem.is_int() == (1, 16r55)

          Type2 (explicitly tagged)
               elem.tag.class == Application
               elem.tag.num == 2
               tagof elem.val == tagof Value.Octets

               The bytes array of the Value.Octets value contains the
               complete encoding of the Type1 value.  The actual value
               can be obtained as follows:

               pick v := elem.val {
               Octets =>
                    (err2, e2) := asn1->decode(v.bytes);

     Page 8                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

               }
               with e2 having exactly the same properties as elem in
               the Type1 case above.

          Type3 (implicitly tagged)
               elem.tag.class == Context
               elem.tag.num == 3
               tagof elem.val == tagof Value.Octets

               In this case the bytes array of the Value.Octets value
               contains the encoding of just the value part of the
               Type1 value, not the complete encoding.  The actual
               value can be obtained as follows:

               pick v := e.val {
               Octets =>
                   constr := e.tag.constr;
                   (err, val) := asn1->decode_value(v.bytes, INTEGER, constr);
               }

               Note that the application has to infer the type of the
               value from the context in which it occurs.  The resul-
               tant val is of the type Value.Int with the value 16r55
               stored in the v member variable.

        Encoding Tagged Values
          To encode the value 16r55 in each of the above types, the
          following data structures are required.

          Type1(primitive type)
               tag := Tag(Universal, INTEGER, 0);
               val := Value.Int(16r55);
               elem := ref Elem(tag, val);
               (err, data) := asn1->encode(elem);

          Type2(explicitly tagged)
               tag1          := Tag(Universal, INTEGER, 0);
               val1          := Value.Int(16r55);
               elem1         := ref Elem(tag1, val1);
               (err1, data1) := asn1->encode(elem1);
               tag2          := Tag(Application, 2, 0);
               val2          := Value.Octets(data1);
               elem2         := ref Elem(tag2, val2);
               (err, data)   := asn1->encode(elem2);

          Type3(implicitly tagged)
               tag         := Tag(Context, 3, 0);
               val         := Value.Int(16r55);
               elem        := ref Elem(tag, val);
               (err, data) := asn1->encode(elem);

     SOURCE

     Page 9                       Plan 9            (printed 12/21/24)

     ASN1(2)                                                   ASN1(2)

          /appl/lib/asn1.b

     BUGS
          It is irritating that REAL values are not converted by the
          module.  This forces the application to do the conversion to
          and from the raw BER encoding.  Fortunately they are rarely
          used.

          String encodings are converted as UTF-8 byte sequences.
          This will result in strings comprising any character codes
          above 127 being incorrectly converted.

          There is a particular form of BER encoding that the module
          will handle incorrectly, resulting in a decoding error.  The
          error occurs when a tagged value is encoded using the indef-
          inite length specifier and the constructed representation.

     Page 10                      Plan 9            (printed 12/21/24)