(**************************************************************************)
(*                                                                        *)
(*  Decoding and encoding an ASN.1 coded file                             *)
(*  Copyright (C) 2024   Peter Moylan                                     *)
(*                                                                        *)
(*  This program is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation, either version 3 of the License, or     *)
(*  (at your option) any later version.                                   *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful,       *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU General Public License for more details.                          *)
(*                                                                        *)
(*  You should have received a copy of the GNU General Public License     *)
(*  along with this program.  If not, see <http://www.gnu.org/licenses/>. *)
(*                                                                        *)
(*  To contact author:   http://www.pmoylan.org   peter@pmoylan.org       *)
(*                                                                        *)
(**************************************************************************)

IMPLEMENTATION MODULE ASN1;

        (********************************************************)
        (*                                                      *)
        (*   Extracting information from an ASN.1 coded file    *)
        (*                                                      *)
        (*  Programmer:         P. Moylan                       *)
        (*  Started:            29 September 2018               *)
        (*  Last edited:        10 October 2024                 *)
        (*  Status:             Working so far                  *)
        (*                                                      *)
        (*  Still need to review ExtractParts to see that it    *)
        (*  handles the varlength case correctly.               *)
        (*                                                      *)
        (********************************************************)


FROM SYSTEM IMPORT CARD8, ADR;

IMPORT BigNum;

FROM BigNum IMPORT BN;

FROM VarStrings IMPORT
    (* type *)  VarStringPtr, ByteStringPtr, ByteStr,
    (* proc *)  MakeBS;

FROM LowLevel IMPORT
    (* proc *)  IAND, Copy, EVAL;

FROM Storage IMPORT
    (* proc *)  ALLOCATE, DEALLOCATE;

FROM STextIO IMPORT
    (* proc *)  WriteString, WriteLn;

(************************************************************************)

CONST
    Nul = CHR(0);
    LF = CHR(10);

TYPE
    (* A record type to keep track of a variable-sized string.  The     *)
    (* size field gives actual string length, and allocated shows how   *)
    (* much space has been allocated.                                   *)

    LongString = RECORD
                    size, allocated: CARDINAL;
                    pval: VarStringPtr;
                 END (*RECORD*);

(************************************************************************)
(*                   OPERATIONS ON TYPE LongString                      *)
(************************************************************************)

PROCEDURE Initialise (VAR (*INOUT*) str: LongString);

    (* Sets str to an empty string. *)

    BEGIN
        str.size := 0;
        str.allocated := 0;
        str.pval := NIL;
    END Initialise;

(************************************************************************)

PROCEDURE Resize (VAR (*INOUT*) str: LongString;  newsize: CARDINAL);

    (* We contract or expand the allocated space, if necessary, *)
    (* to size newsize.                                         *)

    VAR q: VarStringPtr;

    BEGIN
        IF newsize <> str.allocated THEN
            ALLOCATE (q, newsize);
            IF str.size > 0 THEN
                IF newsize > str.size THEN
                    Copy (str.pval, q, str.size);
                ELSE
                    Copy (str.pval, q, newsize);
                END (*IF*);
            END (*IF*);
            IF str.allocated > 0 THEN
                DEALLOCATE (str.pval, str.allocated);
            END (*IF*);
            str.pval := q;
            str.allocated := newsize;
        END (*IF*);
    END Resize;

(************************************************************************)

PROCEDURE Checksize (VAR (*INOUT*) str: LongString;  incr: CARDINAL);

    (* We expand str, if necessary, to allow at least an additional     *)
    (* incr bytes.  For efficiency we usually expand by more than was   *)
    (* requested; so, after you've finished building str, you must call *)
    (* Finalise to contract back to the final size.                     *)

    CONST DefaultIncrement = 256;

    BEGIN
        IF str.size + incr > str.allocated THEN
            IF incr < DefaultIncrement THEN
                incr := DefaultIncrement;
            END (*IF*);
            Resize (str, str.size + incr);
        END (*IF*);
    END Checksize;

(************************************************************************)

PROCEDURE Finalise (VAR (*INOUT*) str: LongString);

    (* Removes any unused but allocated memory. *)

    BEGIN
        Resize (str, str.size+1);
        str.pval^[str.size] := Nul;
    END Finalise;

(************************************************************************)

PROCEDURE AddChar (VAR (*INOUT*) str: LongString;  ch: CHAR);

    (* Appends ch to str. *)

    BEGIN
        Checksize (str, 1);
        str.pval^[str.size] := ch;
        INC (str.size);
    END AddChar;

(************************************************************************)

PROCEDURE AddString (VAR (*INOUT*) str: LongString;  toadd: ARRAY OF CHAR);

    (* Appends toadd to str. *)

    VAR j, N: CARDINAL;

    BEGIN
        N := LENGTH(toadd);
        IF N > 0 THEN
            Checksize (str, N);
            FOR j := 0 TO N-1 DO
                AddChar (str, toadd[j]);
            END (*FOR*);
        END (*IF*);
    END AddString;

(************************************************************************)
(*                     MISCELLANOUS USEFUL PROCEDURES                   *)
(************************************************************************)

PROCEDURE Spaces (VAR (*INOUT*) str: LongString;  amount: CARDINAL);

    (* Appends amount space characters. *)

    VAR j: CARDINAL;

    BEGIN
        FOR j := 1 TO amount DO
            AddChar (str, ' ');
        END (*FOR*);
    END Spaces;

(********************************************************************************)

PROCEDURE AddHexChar (VAR (*INOUT*) str: LongString;  N: CARD8);

    (* Appends a single hexadecimal digit to str.  *)

    BEGIN
        IF N > 9 THEN
            AddChar (str, CHR(ORD('A') + (N - 10)));
        ELSE
            AddChar (str, CHR(ORD('0') + N));
        END (*IF*);
    END AddHexChar;

(************************************************************************)

PROCEDURE AddHex (VAR (*INOUT*) str: LongString;  N: CARD8);

    (* Appends N in hexadecimal.  *)

    BEGIN
        AddHexChar (str, N DIV 16);
        AddHexChar (str, N MOD 16);
    END AddHex;

(************************************************************************)

PROCEDURE AddHexString (VAR (*INOUT*) str: LongString;
                        VAR (*IN*) buf: ARRAY OF CARD8;  pos, N: CARDINAL;
                                         initialindent, indent: CARDINAL);

    (* Append N bytes from buf[pos].  *)

    VAR j, canfit: CARDINAL;

    BEGIN
        canfit := (80 - initialindent) DIV 3;
        IF N <= canfit THEN
            FOR j := 0 TO N-1 DO
                AddHex (str, buf[pos]);  INC(pos);
                AddChar (str, ' ');
            END (*FOR*);
        ELSE
            AddChar (str, LF);
            canfit := (80 - indent) DIV 3;
            WHILE N > 0 DO
                Spaces (str, indent);
                j := 0;
                REPEAT
                    AddHex (str, buf[pos]);  INC(pos);
                    AddChar (str, ' ');
                    DEC (N);  INC (j);
                UNTIL (N = 0) OR (j = canfit);
                IF N > 0 THEN
                    AddChar (str, LF);
                END (*IF*);
            END (*WHILE*);
        END (*IF*);
    END AddHexString;

(************************************************************************)

PROCEDURE AddAscString (VAR (*INOUT*) str: LongString;
                        VAR (*IN*) buf: ARRAY OF CARD8;  pos, N: CARDINAL);

    (* Like AddHexString, but we interpret the bytes as characters.  *)

    VAR j: CARDINAL;

    BEGIN
        FOR j := 0 TO N-1 DO
            AddChar (str, CHR(buf[pos]));  INC(pos);
        END (*FOR*);
    END AddAscString;

(************************************************************************)

PROCEDURE AddCharString (VAR (*INOUT*) str: LongString;
                                VAR (*IN*) buf: ARRAY OF CARD8;
                                                    pos, N: CARDINAL);

    (* Append character string, N bytes starting from buffer[pos].      *)
    (*    option = 1        ASCII                                       *)
    (*    option = 2        UTF8                                        *)

    BEGIN
        (* For now I'm ignoring the option. *)

        IF N > 0 THEN
            AddAscString (str, buf, pos, N);
        END (*IF*);

    END AddCharString;

(************************************************************************)

PROCEDURE AddCard (VAR (*INOUT*) str: LongString;  N: CARDINAL);

    (* Append N in decimal.  *)

    BEGIN
        IF N > 9 THEN
            AddCard (str, N DIV 10);
            N := N MOD 10;
        END (*IF*);
        AddChar (str, CHR(ORD('0') + N));
    END AddCard;

(************************************************************************)

PROCEDURE AddBignum (VAR (*INOUT*) str: LongString;  N: BN);

    (* Append N in decimal.  *)

    VAR buffer: ARRAY [0..2047] OF CHAR;
        pos: CARDINAL;

    BEGIN
        pos := 0;
        BigNum.ToDecimal (N, buffer, pos);
        AddString (str, buffer);
    END AddBignum;

(************************************************************************)

PROCEDURE UpdateNum (VAR (*INOUT*) A: BN;  base, nextdigit: CARDINAL);

    (* A := base*A + nextdigit *)

    VAR B, C, D, E: BN;

    BEGIN
        B := BigNum.MakeBignum(base);
        C := BigNum.Prod (A, B);
        D := BigNum.MakeBignum (nextdigit);
        E := BigNum.Sum (C, D);
        BigNum.Discard (A);
        BigNum.Discard (B);
        BigNum.Discard (C);
        BigNum.Discard (D);
        A := E;
    END UpdateNum;

(************************************************************************)
(*                TRANSLATE BINARY ASN.1 TO HUMAN-READABLE              *)
(************************************************************************)

PROCEDURE ParseASN1 (VAR (*IN*) buffer: ARRAY OF CARD8;
                        VAR (*INOUT*) pos: CARDINAL;  N, indent: CARDINAL;
                                  VAR (*INOUT*) str: LongString); FORWARD;

    (* Translates ASN.1 code starting at buffer[pos], total N bytes.    *)
    (* We update pos.                                                   *)

(************************************************************************)

PROCEDURE AddInt (VAR (*INOUT*) str: LongString;  val: BN;
                                            length: CARDINAL);

    (* Appends a decimal integer value to str. *)

    (* I have not yet pretty-formatted the result in the case of line   *)
    (* overflow.  It might turn out that the input parameter length     *)
    (* is irrelevant.                                                   *)

    VAR amount, pos: CARDINAL;
        p: VarStringPtr;

    BEGIN
        amount := BigNum.Digits(val) + 4;       (* safety margin! *)
        Checksize (str, amount);
        ALLOCATE (p, amount);
        pos := 0;
        BigNum.ToDecimal (val, p^, pos);
        IF length <= 16 THEN
            Copy (p, ADR(str.pval^[str.size]), pos);
            INC (str.size, pos);
        ELSE
            Copy (p, ADR(str.pval^[str.size]), pos);
            INC (str.size, pos);
        END (*IF*);
        DEALLOCATE (p, amount);
    END AddInt;

(************************************************************************)

PROCEDURE ParseSEQ (VAR (*IN*) buffer: ARRAY OF CARD8;
                        VAR (*INOUT*) pos: CARDINAL;  N, indent: CARDINAL;
                        VAR (*INOUT*) str: LongString);

    (* Translates a sequence.    *)

    VAR nextpos: CARDINAL;

    BEGIN
        nextpos := pos + N;
        Spaces (str, indent);  AddChar (str, '{');  AddChar (str, LF);
        WHILE pos < nextpos DO
            ParseASN1 (buffer, pos, N, indent, str);
        END (*WHILE*);
        Spaces (str, indent);  AddChar (str, '}');  AddChar (str, LF);
    END ParseSEQ;

(************************************************************************)

PROCEDURE ParseSET (VAR (*IN*) buffer: ARRAY OF CARD8;
                        VAR (*INOUT*) pos: CARDINAL;  N, indent: CARDINAL;
                        VAR (*INOUT*) str: LongString);

    (* Translates a set.    *)

    VAR nextpos: CARDINAL;

    BEGIN
        nextpos := pos + N;
        Spaces (str, indent);  AddChar (str, '{');  AddChar (str, LF);
        WHILE pos < nextpos DO
            ParseASN1 (buffer, pos, N, indent, str);
        END (*WHILE*);
        Spaces (str, indent);  AddChar (str, '}');  AddChar (str, LF);
    END ParseSET;

(********************************************************************)

PROCEDURE Fetch (VAR (*IN*) buffer: ARRAY OF CARD8;
                               VAR (*INOUT*) pos: CARDINAL): CARDINAL;

    (* Gets next byte from buffer, updates pos. *)

    VAR val: CARD8;

    BEGIN
        val := buffer[pos];  INC(pos);
        RETURN val;
    END Fetch;

(************************************************************************)

PROCEDURE GetInt (VAR (*IN*) buffer: ARRAY OF CARD8;
                         VAR (*INOUT*) pos: CARDINAL;  length: CARDINAL): BN;

    (* Picks up a Bignum integer of length bytes.  Remark: I should *)
    (* allow for negative numbers in twos complement notation, but  *)
    (* so far I haven't done this.                                  *)

    VAR result: BN;

    (********************************************************************)

    PROCEDURE AppendNextByte (VAR (*INOUT*) A: BN);

        (* A := 256*A + Fetch() *)

        BEGIN
            UpdateNum (A, 256, Fetch(buffer, pos));
        END AppendNextByte;

    (********************************************************************)

    BEGIN
        result := BigNum.Zero();
        WHILE length > 0 DO
            AppendNextByte (result);
            DEC (length);
        END (*WHILE*);
        RETURN result;
    END GetInt;

(************************************************************************)

PROCEDURE TypeAndLength (VAR (*IN*) buffer: ARRAY OF CARD8;
                                        VAR (*INOUT*) pos: CARDINAL;
                         VAR (*OUT*) type, class: CARDINAL;
                         VAR (*OUT*) constructed, varlength: BOOLEAN;
                         VAR (*OUT*) length: CARDINAL): CARDINAL;

    (* Fetches the initial type/length values at buffer[pos], updates   *)
    (* pos.  This is the initial two bytes in the simplest case, but    *)
    (* more in some cases.  The type value is broken down into type,    *)
    (* class, and constructed for the convenience of the caller.   In   *)
    (* the special case varlength=TRUE we still set length to the size  *)
    (* of the contents, but the caller might need to know that there    *)
    (* are two zero bytes, which have not been counted in the length,   *)
    (* as a terminator coming after the value.                          *)

    (* The function result is the number of bytes consumed.  This does  *)
    (* not include the content bytes, only the type and length.         *)

    (********************************************************************)

    VAR pos0, j, k, lengthcode: CARDINAL;
        temp: CARD8;
        last: BOOLEAN;

    BEGIN
        pos0 := pos;

        (* First byte has type information.  In ASN.1 notation this is  *)
        (* called a tag.  Universal tags have numbers in the range 1 to *)
        (* 30 and class=0.  Other values are application-dependent.     *)

        type := Fetch(buffer, pos);
        class := type DIV 64;
        constructed := IAND (type, 32) <> 0;
        type := IAND (type, 31);
        IF type = 31 THEN
            type := 0;
            REPEAT
                temp := Fetch(buffer, pos);
                last := IAND(temp, 128) = 0;
                type := 128*type + IAND(temp,127);
            UNTIL last;
        END (*IF*);

        (* Second byte has length, or code to indicate long length.     *)
        (* The special case where varlength = TRUE means indefinite     *)
        (* length, in which case the value is terminated by two         *)
        (* consecutive zero bytes.                                      *)

        lengthcode := Fetch(buffer, pos);
        varlength := lengthcode = 128;
        length := 0;
        IF varlength THEN
            (* Two zero bytes terminate the content.  We do not include *)
            (* these in the calculated length.                          *)
            k := pos;  length := 0;
            WHILE (buffer[k] <> 0) OR (buffer[k+1] <> 0) DO
                INC (k);  INC (length);
            END (*WHILE*);
        ELSIF lengthcode < 128 THEN
            length := lengthcode;
        ELSE
            (* Long form for length. *)
            temp := lengthcode - 128;
            FOR j := 0 TO temp-1 DO
                length := 256*length + buffer[pos];
                INC (pos);
            END (*FOR*);
        END (*IF*);

        RETURN pos-pos0;

    END TypeAndLength;

(************************************************************************)

PROCEDURE ParseASN1 (VAR (*IN*) buffer: ARRAY OF CARD8;
                        VAR (*INOUT*) pos: CARDINAL;  N, indent: CARDINAL;
                        VAR (*INOUT*) str: LongString);

    (* Translates ASN.1 code starting at buffer[pos], total N bytes.    *)
    (* We update pos.                                                   *)

    VAR shortlength: CARDINAL;

    (********************************************************************)

    PROCEDURE AppendNextByte (VAR (*INOUT*) A: BN);

        (* A := 256*A + Fetch() *)

        BEGIN
            UpdateNum (A, 256, Fetch(buffer, pos));
        END AppendNextByte;

    (********************************************************************)

    PROCEDURE GetInt (length: CARDINAL): BN;

        (* Picks up an integer of length bytes.  Remark: I should allow *)
        (* for negative numbers in twos complement notation, but so far *)
        (* I haven't done this.                                         *)

        VAR result: BN;

        BEGIN
            result := BigNum.Zero();
            WHILE length > 0 DO
                AppendNextByte (result);
                DEC (length);
            END (*WHILE*);
            RETURN result;
        END GetInt;

    (********************************************************************)

    PROCEDURE Skip (length: CARDINAL;  variable: BOOLEAN);

        (* Jumps over a value we don't want to interpret.  This is used *)
        (* for types we don't yet recognise, and in cases when an       *)
        (* operation like AddCharString has not advanced the buffer     *)
        (* position.                                                    *)

        VAR val: CARD8;

        BEGIN
            IF variable THEN
                LOOP
                    val := buffer[pos];  INC (pos);  DEC (N);
                    IF val = 0 THEN
                        val := buffer[pos];  INC (pos);  DEC (N);
                        IF val = 0 THEN EXIT (*LOOP*) END(*IF*);
                    END (*IF*);
                END (*LOOP*);
            ELSE
                INC (pos, length);  DEC (N, length);
            END (*IF*);
        END Skip;

    (********************************************************************)

    PROCEDURE GenString (tag: CARDINAL);

        VAR label: ARRAY [0..15] OF CHAR;

        BEGIN
            CASE tag OF
               | 12:    label := "UTF8String";
               | 18:    label := "NumericString";
               | 19:    label := "PrintableString";
               | 20:    label := "TeletextString";
               | 21:    label := "VideoTextString";
               | 22:    label := "IA5String";
               | 25:    label := "GraphicString";
               | 26:    label := "VisibleString";
               | 27:    label := "GeneralString";
               | 28:    label := "UniversalString";
               | 29:    label := "CHARACTER STRING";
               | 30:    label := "BMPString";
            ELSE
                        label := "???";
            END (*IF*);
            AddString (str, label);
            AddString (str, ' ');
            AddCharString (str, buffer, pos, shortlength);
            AddChar (str, LF);
            Skip (shortlength, FALSE);
        END GenString;

(************************************************************************)

    VAR type, class, j, indentp3: CARDINAL;
        val: BN;
        temp: CARD8;
        constructed, varlength, ShowDetails: BOOLEAN;

    BEGIN
        indentp3 := indent + 3;

        DEC (N, TypeAndLength (buffer, pos, type, class,
                                        constructed, varlength, shortlength));

        Spaces (str, indent);
        IF class = 0 THEN
            CASE type OF
                    1..6, 12, 16..23, 25..30:
                            ShowDetails := FALSE;
                    ELSE    ShowDetails := TRUE;
            END (*IF*);
        ELSE
            ShowDetails := FALSE;
        END (*IF*);

        IF ShowDetails THEN
            (* Say what we have so far. *)

            AddString (str, "class=");  AddCard (str, class);
            AddString (str, ", type=");  AddCard (str, type);
            IF constructed THEN AddString (str, " constructed") END(*IF*);
            AddString (str, ", length=");
            IF varlength THEN
                AddString (str, "indefinite");
            ELSE
                AddCard (str, shortlength);
            END (*IF*);
            AddChar (str, ' ');
        END (*IF*);

        IF shortlength = MAX(CARDINAL) THEN
            AddString (str, "Implausible length, aborting translation");  AddChar (str, LF);
            RETURN;
        END (*IF*);

        IF class = 1 THEN
            AddString (str, "AppType ");  AddCard (str, type);
            AddString (str, ":  ");
            AddHexString (str, buffer, pos, shortlength, indent+12, indentp3);
            INC (pos, shortlength);  AddChar (str, LF);
        ELSIF class = 2 THEN
            AddChar (str, '[');  AddCard (str, type);
            AddString (str, "] {");  AddChar (str, LF);
            ParseASN1 (buffer, pos, shortlength, indentp3, str);
            Spaces (str, indentp3);  AddChar (str, '}');  AddChar (str, LF);
        ELSIF class = 3 THEN
            AddString (str, "PrivateType ");  AddCard (str, type);
            AddString (str, ":  ");
            AddHexString (str, buffer, pos, shortlength, indent+16, indentp3);
            INC (pos, shortlength);  AddChar (str, LF);
        ELSE            (* class 0 is Universal, the most common class *)

            (* Note that most numeric types are expressed in hexadecimal,   *)
            (* but we do choose to convert INTEGER to decimal.              *)

            CASE type OF
                |   1:  (* BOOLEAN *)
                        AddString (str, "BOOLEAN ");
                        IF Fetch(buffer, pos) = 0 THEN
                            AddString (str, "FALSE");
                        ELSE
                            AddString (str, "TRUE");
                        END (*IF*);
                        AddChar (str, LF);

                |   2:  (* INTEGER *)
                        AddString (str, "INTEGER ");
                        val := GetInt(shortlength);  AddInt (str, val, shortlength);
                        AddChar (str, LF);
                        BigNum.Discard (val);

                |   3:  (* BIT STRING *)
                        (* N.B. constructed encoding has to be handled              *)
                        (* differently, but this should not occur in DER encoding.  *)
                        AddString (str, "BIT STRING ");  AddCard (str, Fetch(buffer, pos));
                        DEC (shortlength);  AddString (str, " unused bits");
                        AddHexString (str, buffer, pos, shortlength, indent+12, indentp3);
                        Skip (shortlength, FALSE);  AddChar (str, LF);

                |   4:  (* OCTET STRING *)
                        (* N.B. constructed encoding has to be handled              *)
                        (* differently, but this should not occur in DER encoding.  *)
                        AddString (str, "OCTET STRING ");
                        AddHexString (str, buffer, pos, shortlength, indent+13, indentp3);
                        Skip (shortlength, FALSE);  AddChar (str, LF);

                |   5:  (* NULL *)
                        AddString (str, "NULL");  AddChar (str, LF);
                        (* Zero content, zero length. *)

                |   6:  (* OBJECT IDENTIFIER *)
                        AddString (str, "OBJECT IDENTIFIER {");
                        temp := Fetch(buffer, pos);  AddCard(str, temp DIV 40);  AddChar (str, ' ');
                        AddCard (str, temp MOD 40);
                        DEC (shortlength);
                        WHILE shortlength > 0 DO
                            val := BigNum.Zero();
                            LOOP
                                j := Fetch(buffer, pos);
                                DEC(shortlength);
                                UpdateNum (val, 128, IAND (j, 7FH));
                                IF IAND (j, 80H) = 0 THEN
                                    EXIT (*LOOP*);
                                END (*IF*);
                            END (*LOOP*);
                            AddChar (str, ' ');  AddBignum (str, val);
                        END (*IF*);
                        AddChar (str, '}');  AddChar (str, LF);

                |   7:  (* ObjectDescriptor, not yet handled *)
                        AddString (str, "ObjectDescriptor, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |   8:  (* INSTANCE OF, EXTERNAL, not yet handled *)
                        AddString (str, "INSTANCE OF, EXTERNAL, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |   9:  (* REAL, not yet handled *)
                        AddString (str, "REAL, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |  10:  (* ENUMERATED, not yet handled *)
                        AddString (str, "ENUMERATED, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |  11:  (* EMBEDDED PDV, not yet handled *)
                        AddString (str, "EMBEDDED PDV, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |  12, 18, 19, 20, 21, 22, 25, 26, 27, 28, 29, 30:
                        (* Various string types *)
                        GenString (type);

                |  13:  (* RELATIVE-OID, not yet handled *)
                        AddString (str, "RELATIVE-OID, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                |  16:  AddString (str, "SEQUENCE");  AddChar (str, LF);
                        ParseSEQ (buffer, pos, shortlength, indentp3, str);

                |  17:  AddString (str, "SET");  AddChar (str, LF);
                        ParseSET (buffer, pos, shortlength, indentp3, str);

                |  23:  (* UTCTime *)
                        (* N.B. constructed encoding has to be handled              *)
                        (* differently, but this should not occur in DER encoding.  *)
                        AddString (str, 'UTCTime ');
                        AddCharString (str, buffer, pos, 1);
                        AddChar (str, LF);
                        Skip (shortlength, FALSE);

                |  24:  (* GeneralizedTime, not yet handled *)
                        AddString (str, "GeneralizedTime, val=");
                        AddString (str, "not yet implemented");  AddChar (str, LF);
                        Skip (shortlength, varlength);

                ELSE
                        AddChar (str, LF);  AddString (str, "UNKNOWN TYPE ");
                        AddCard (str, type);  AddChar (str, LF);
                        Skip (shortlength, varlength);
            END (*CASE*);
        END (*IF*);

    END ParseASN1;

(************************************************************************)
(*                  TRANSLATING ASN.1 TO HUMAN-READABLE                 *)
(************************************************************************)

PROCEDURE ASN1toTxt (VAR (*IN*) bindata: ARRAY OF CARD8;  N: CARDINAL;
                            VAR (*OUT*) textptr: VarStringPtr): CARDINAL;

    (* Translates N binary bytes in ASN.1 format to text.  Returns  *)
    (* the number of text characters.                               *)

    CONST BufSize = 65536;

    VAR str: LongString;
        pos: CARDINAL;

    BEGIN
        pos := 0;
        Initialise (str);
        ParseASN1 (bindata, pos, N, 0, str);
        Finalise (str);
        textptr := str.pval;
        RETURN str.size;
    END ASN1toTxt;

(************************************************************************)
(*                EXTRACTING CONTENT FROM AN ASN.1 ELEMENT              *)
(************************************************************************)

PROCEDURE CopyContent (VAR (*IN*) bindata: ARRAY OF CARD8): ByteStr;

    (* Throws away the type infomation of an ASN.1 element, returning   *)
    (* only the content part.  Special case: for a BIT STRING element,  *)
    (* we discard the "unused bits" field, because this is always zero  *)
    (* in the applications I'm interested in.                           *)

    (* NOTE: this returns a copy of the content,  not merely a pointer  *)
    (* to the data.                                                     *)

    VAR pos, type, class, length, k: CARDINAL;
        result: ByteStr;
        constructed, varlength: BOOLEAN;

    BEGIN
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        IF varlength THEN
            (* Two zero bytes terminate the content. *)
            k := pos;  length := 0;
            WHILE (bindata[k] <> 0) OR (bindata[k+1] <> 0) DO
                INC (k);  INC (length);
            END (*WHILE*);
        END (*IF*);
        IF type = 3 THEN        (* BIT STRING *)
            INC (pos);  DEC (length);
        END (*IF*);
        MakeBS (result, length);
        Copy (ADR(bindata[pos]), result.data, length);
        RETURN result;
    END CopyContent;

(************************************************************************)

PROCEDURE GetValueString (VAR (*IN*) bindata: ARRAY OF CARD8): ByteStr;

    (* Throws away the type infomation of an ASN.1 element, returning   *)
    (* only the content part.  Special case: for a BIT STRING element,  *)
    (* we discard the "unused bits" field, because this is always zero  *)
    (* in the applications I'm interested in.                           *)

    VAR pos, type, class, length: CARDINAL;
        result: ByteStr;
        constructed, varlength: BOOLEAN;

    BEGIN
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        IF type = 3 THEN        (* BIT STRING *)
            INC (pos);  DEC (length);
        END (*IF*);
        result.allocated := 0;
        result.data := ADR(bindata[pos]);
        result.size := length;
        RETURN result;
    END GetValueString;

(************************************************************************)

PROCEDURE GetBool (VAR (*IN*) bindata: ARRAY OF CARD8): BOOLEAN;

    (* Fetches a Boolean value. *)

    VAR val: ByteStr;

    BEGIN
        val := GetValueString (bindata);
        RETURN val.data^[0] <> 0;
    END GetBool;

(************************************************************************)

PROCEDURE GetInteger (VAR (*IN*) bindata: ARRAY OF CARD8): BN;

    (* Assumption: bindata is an ASN.1 encoding of an integer.  If so,  *)
    (* we return the value as a BigNum.  If not, we return zero.        *)

    VAR pos, type, class, length: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        IF (type <> 2) OR varlength THEN
            RETURN BigNum.Zero();
        END (*IF*);

        RETURN BigNum.BinToBN (bindata, pos, length);

    END GetInteger;

(************************************************************************)

PROCEDURE GetSmallInteger (VAR (*IN*) bindata: ARRAY OF CARD8): INTEGER;

    (* Assumption: bindata is an ASN.1 encoding of an integer.  If so,  *)
    (* we return the value.  If not, we return zero.  The result is     *)
    (* saturated if it overflows.                                       *)

    VAR pos, type, class, length: CARDINAL;
        constructed, varlength: BOOLEAN;
        bigval: BN;

    BEGIN
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        IF (type <> 2) OR varlength THEN
            RETURN 0;
        END (*IF*);

        bigval := BigNum.BinToBN (bindata, pos, length);
        RETURN BigNum.Int(bigval);

    END GetSmallInteger;

(************************************************************************)
(*            EXTRACTING PARTS OF AN ASN.1 BINARY STRING                *)
(************************************************************************)

PROCEDURE ExtractParts (VAR (*IN*) bindata: ARRAY OF CARD8;
                        VAR (*OUT*) result: ARRAY OF ByteStr): CARDINAL;

    (* If bindata represents a SEQUENCE or a SET, result[k] contains    *)
    (* a copy of the kth component of the sequence or set.  The         *)
    (* function result is the number of components extracted.           *)
    (* Otherwise, no result is extracted, we return 0 as the function   *)
    (* result.  Note that some components will not be extracted if the  *)
    (* result array is not big enough.                                  *)

    (* In this version we don't make a copy for the result; we simply   *)
    (* point into the original bindata array.                           *)

    VAR pos0, pos, type, class, count, hsize, M, N, length: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        (* Read the overall type/length information. *)

        count := 0;
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));

        IF (type <> 0) AND ((type < 16) OR (type > 17)) THEN
            (* Not a sequence or set. *)
            RETURN 0;
        END (*IF*);

        N := length;       (* bytes left to go *)
        M := 0;

        WHILE N > M DO

            DEC (N, M);

            (* Get the next component. *)

            pos0 := pos;
            hsize := TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length);

            M := length;
            INC (pos, M);
            INC (M, hsize);

            WITH result[count] DO
                allocated := 0;
                size := M;
                data := ADR(bindata[pos0]);
            END (*WITH*);
            IF varlength THEN
                INC (pos, 2);
            END (*IF*);
            INC (count);

        END (*WHILE*);

        RETURN count;

    END ExtractParts;

(************************************************************************)

PROCEDURE GetTaggedItem (VAR (*IN*) bindata: ARRAY OF CARD8;
                        VAR (*OUT*) result: ByteStr): CARDINAL;

    (* Assumption: we have an EXPLICIT tag.  Removes the tag and        *)
    (* length, returning the tag value as well as the result.           *)

    (* We ought to be able to do something similar with an implicit     *)
    (* tag, but I don't yet have a good enough reference for ASN.1.     *)

    VAR pos, type, class, length: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        pos := 0;
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        WITH result DO
            allocated := 0;
            size := length - pos;
            data := ADR(bindata[pos]);
        END (*WITH*);

        RETURN type;

    END GetTaggedItem;

(************************************************************************)

PROCEDURE INTEGERtoBN (VAR (*IN*) bindata: ARRAY OF CARD8;  pos: CARDINAL): BN;

    (* Converts an ASN.1 INTEGER to a Bignum.  Returns zero if an   *)
    (* INTEGER does not start at bindata[pos].                      *)

    VAR type, class, length: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, length));
        IF type <> 2 THEN
            (* Not an integer. *)
            RETURN BigNum.Zero();
        END (*IF*);
        RETURN GetInt (bindata, pos, length);
    END INTEGERtoBN;

(************************************************************************)

PROCEDURE BitStringToBN (VAR (*IN*) bindata: ARRAY OF CARD8;  pos: CARDINAL): BN;

    (* Converts an ASN.1 BIT STRING to a Bignum.  Returns zero if a BIT *)
    (* STRING does not start at bindata[pos].                           *)

    VAR type, class, shortlength, scale: CARDINAL;
        constructed, varlength: BOOLEAN;
        val: BN;

    BEGIN
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, shortlength));
        IF type <> 3 THEN
            (* Not a bit string. *)
            RETURN BigNum.Zero();
        END (*IF*);
        scale := bindata[pos];  INC(pos);  DEC (shortlength);
        val := BigNum.BinToBN (bindata, pos, shortlength);
        IF scale > 0 THEN
            BigNum.RSBN (val, scale);
        END (*IF*);
        RETURN val;
    END BitStringToBN;

(************************************************************************)

PROCEDURE GetText (VAR (*IN*) bindata: ARRAY OF CARD8;  pos: CARDINAL;
                           VAR (*OUT*) result: ARRAY OF CHAR);

    (* Converts a component that is a textual item.  We trust the       *)
    (* caller to know that the type is appropriate.                     *)

    VAR type, class, shortlength: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, shortlength));
        Copy (ADR(bindata[pos]), ADR(result), shortlength);
        IF HIGH(result) >= shortlength THEN
            result[shortlength] := CHR(0);
        END (*IF*);
    END GetText;

(************************************************************************)

PROCEDURE GetOctetString (VAR (*IN*) bindata: ARRAY OF CARD8;  pos: CARDINAL;
                           VAR (*OUT*) result: ByteStr);

    (* Converts a component that is a string of bytes.  We trust the    *)
    (* caller to know that the type is appropriate.                     *)

    VAR type, class, shortlength: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, shortlength));
        result.allocated := 0;
        result.size := shortlength;
        result.data := ADR(bindata[pos]);
    END GetOctetString;

(************************************************************************)

PROCEDURE GetObjID (VAR (*IN*) bindata: ARRAY OF CARD8;  pos: CARDINAL;
                           VAR (*OUT*) result: ARRAY OF CARDINAL): CARDINAL;

    (* Converts an ASN.1 BIT STRING to an OBJECT ID.  Returns the       *)
    (* number of components, or zero if an object ID does not start at  *)
    (* bindata[pos].                                                    *)

    VAR type, class, shortlength, val, temp, j, N: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        EVAL (TypeAndLength (bindata, pos, type, class,
                                        constructed, varlength, shortlength));
        IF type <> 6 THEN
            (* Not an object ID. *)
            RETURN 0;
        END (*IF*);

        temp := Fetch(bindata, pos);
        DEC (shortlength);
        result[0] := temp DIV 40;
        result[1] := temp MOD 40;
        N := 2;
        WHILE shortlength > 0 DO
            val := 0;
            LOOP
                j := Fetch(bindata, pos);
                DEC (shortlength);
                val := 128 * val + IAND(j, 127);
                IF IAND (j, 80H) = 0 THEN
                    EXIT (*LOOP*);
                END (*IF*);
            END (*LOOP*);
            result[N] := val;  INC(N);
        END (*WHILE*);
        RETURN N;
    END GetObjID;

(************************************************************************)

PROCEDURE GetNumericArray (VAR (*IN*) buffer: ARRAY OF CARD8;
                            VAR (*OUT*) result: ARRAY OF BN): CARDINAL;

    (* Assumption: bindata represents a SEQUENCE or a SET where each    *)
    (* component is a Bignum integer.  The output result is an array    *)
    (* of those numbers, and the function result is the number of       *)
    (* integers extracted.  If the input assumption is violated, or the *)
    (* result array is not big enough, some results will be zero.       *)

    VAR pos: CARDINAL;

    (********************************************************************)

    PROCEDURE Skip (length: CARDINAL;  variable: BOOLEAN);

        (* Jumps over a value we don't want to interpret.  This should  *)
        (* be relevant only for types we don't yet recognise.           *)

        VAR val: CARD8;

        BEGIN
            IF variable THEN
                LOOP
                    val := buffer[pos];  INC (pos);
                    IF val = 0 THEN
                        val := buffer[pos];  INC (pos);
                        IF val = 0 THEN EXIT (*LOOP*) END(*IF*);
                    END (*IF*);
                END (*LOOP*);
            ELSE
                INC (pos, length);
            END (*IF*);
        END Skip;

    (********************************************************************)

    VAR pos0, type, class, count, N, length: CARDINAL;
        constructed, varlength: BOOLEAN;

    BEGIN
        (* Read the overall type/length information. *)

        count := 0;
        pos := 0;
        EVAL (TypeAndLength (buffer, pos, type, class,
                                        constructed, varlength, length));

        IF (type < 16) OR (type > 17) THEN
            (* Not a sequence or set. *)
            RETURN 0;
        END (*IF*);

        N := length;       (* bytes left to go *)

        WHILE N > 0 DO

            (* Get the next component. *)

            pos0 := pos;
            EVAL (TypeAndLength (buffer, pos, type, class,
                                        constructed, varlength, length));
            IF type = 2 THEN
                result[count] := GetInt (buffer, pos, length);
            ELSE
                (* Not an integer. *)
                result[count] := BigNum.Zero();
                Skip (length, varlength);
            END (*IF*);
            INC (count);

            DEC (N, pos - pos0);

        END (*WHILE*);

        RETURN count;

    END GetNumericArray;

(************************************************************************)
(*                  TRANSLATING DATA TO ASN.1 NOTATION                  *)
(************************************************************************)

(************************************************************************)
(* NOTE: There is a lot of code duplication in the following procedures.*)
(* I should look into how to reduce the duplication.                    *)
(************************************************************************)

PROCEDURE LengthLength (k: CARDINAL): CARDINAL;

    (* Returns the number of bytes needed to encode length k. *)

    (* Rules: values 0..127 can be encoded in one byte.  For larger *)
    (* k, we need one byte, plus as many bytes as are needed to     *)
    (* represent k in binary.                                       *)

    BEGIN
        IF k < 128 THEN RETURN 1
        ELSIF k < 256 THEN RETURN 2
        ELSIF k < 65536 THEN RETURN 3
        ELSIF k < 16777216 THEN RETURN 4
        ELSE RETURN 5
        END (*IF*);
    END LengthLength;

(************************************************************************)

PROCEDURE TotalLength (lval: CARDINAL;  VAR (*OUT*) LL: CARDINAL): CARDINAL;

    (* Returns the total length of an an encoding, given the length of  *)
    (* the contents (lval).  Also returns the LengthLength value.       *)

    BEGIN
        LL := LengthLength (lval);
        RETURN 1 + LL + lval;
    END TotalLength;

(************************************************************************)

PROCEDURE StoreLength (p: ByteStringPtr;  VAR (*INOUT*) pos: CARDINAL;
                                LL, k: CARDINAL);

    (* Stores the length code for k at p^[pos], updates pos.    *)
    (* LL is the number of extra bytes needed if k > 127.       *)

    BEGIN
        IF LL > 0 THEN
            p^[pos] := LL + 80H;  INC(pos);
            IF LL > 1 THEN
                IF LL > 2 THEN
                    IF LL > 3 THEN
                        p^[pos] := k DIV 16777216;  INC(pos);
                        k := k MOD 16777216;
                    END (*IF*);
                    p^[pos] := k DIV 65536;  INC(pos);
                    k := k MOD 65536;
                END (*IF*);
                p^[pos] := k DIV 256;  INC(pos);
                k := k MOD 256;
            END (*IF*);
        END (*IF*);
        p^[pos] := k;  INC(pos);
    END StoreLength;

(************************************************************************)

PROCEDURE ASNEncodeBitString (VAR (*INOUT*) val: ByteStr;  unusedbits: CARD8);

    (* Produces the ASN.1 encoding of a single bit string.  *)
    (* The result replaces the original data.               *)

    (********************************************************************)

    VAR pos, lval, length, LL, LV: CARDINAL;
        q: ByteStringPtr;

    BEGIN
        (* Start by calculating the LL and LV values.  LL is the length *)
        (* if the length field, LV is the length of the value field.    *)
        (* Doing this now will avoid duplicated calculations later.     *)

        lval := val.size + 1;   (* have to allow for the unusedbits field *)
        LL := LengthLength (lval);
        LV := lval;

        (* Work out the length of the encoding. *)

        length := 1 + LL + LV;

        (* Allocate the space. *)

        ALLOCATE (q, length);

        (* Fill in the encoding. *)

        q^[0] := 3;             (* code for BIT STRING *)
        pos := 1;
        StoreLength (q, pos, LL - 1, LV);
        q^[pos] := unusedbits;  INC(pos);
        Copy (val.data, ADR(q^[pos]), val.size);
        INC (pos, val.size);

        (* Deallocate the original data, replace it with new data. *)

        DEALLOCATE (val.data, val.size);
        val.size := length;
        val.allocated := length;
        val.data := q;

    END ASNEncodeBitString;

(************************************************************************)

PROCEDURE ASNEncodeCharString (type: CARDINAL;  str: ARRAY OF CHAR;
                                        VAR (*OUT*) val: ByteStr);

    (* Produces the ASN.1 encoding of a character string.               *)

    (* The restricted character string types are                        *)
    (*    12  UTF8String      18  NumericString    19  PrintableString  *)
    (*    20  TeletextString  21  VideoTextString  22  IA5String        *)
    (*    25  GraphicString   26  VisibleString    27  GeneralString    *)
    (*    28  UniversalString 30  BMPString                             *)
    (* There is also a unrestricted type   29 CHARACTER STRING          *)

    VAR lval, LL, pos: CARDINAL;

    BEGIN
        lval := LENGTH(str);
        val.size := TotalLength (lval, LL);
        val.allocated := val.size;
        ALLOCATE (val.data, val.size);
        val.data^[0] := type;  pos := 1;
        StoreLength (val.data, pos, LL-1, lval);
        Copy (ADR(str), ADR(val.data^[pos]), lval);
    END ASNEncodeCharString;

(************************************************************************)

PROCEDURE ASNEncodeSequence (N: CARDINAL;  val: ARRAY OF ByteStr): ByteStr;

    (* Produces the ASN.1 encoding of a sequence of N components.  We   *)
    (* assume that those components have already been created in the    *)
    (* form of valid ASN.1 strings.                                     *)

    (********************************************************************)

    VAR pos, j, length, lengthV: CARDINAL;
        p: ByteStringPtr;
        result: ByteStr;

    BEGIN
        (* Work out the length of the encoding. *)

        length := 0;
        FOR j := 0 TO N-1 DO
            INC (length, val[j].size);
        END (*FOR*);
        lengthV := length;
        INC (length, LengthLength(length));
        INC (length);

        (* Allocate the space. *)

        result.size := length;
        result.allocated := length;
        ALLOCATE (result.data, length);

        (* Fill in the encoding. *)

        result.data^[0] := 30H;  pos := 1;        (* code for SEQUENCE *)
        StoreLength (result.data, pos, LengthLength(lengthV)-1, lengthV);
        FOR j := 0 TO N-1 DO
            p := ADR(result.data^[pos]);
            Copy (val[j].data, p, val[j].size);
            INC (pos, val[j].size);
        END (*FOR*);

        RETURN result;

    END ASNEncodeSequence;

(************************************************************************)

PROCEDURE ASNEncodeSet (N: CARDINAL;  val: ARRAY OF ByteStr): ByteStr;

    (* Produces the ASN.1 encoding of a set of N components.  We        *)
    (* assume that those components have already been created in the    *)
    (* form of valid ASN.1 strings.                                     *)

    (* Note that this procedure is almost identical to procedure        *)
    (* ASNEncodeSequence.  We could probably find a more compact        *)
    (* implementation.                                                  *)

    (********************************************************************)

    VAR pos, j, length, lengthV: CARDINAL;
        p: ByteStringPtr;
        result: ByteStr;

    BEGIN
        (* Work out the length of the encoding. *)

        length := 0;
        FOR j := 0 TO N-1 DO
            INC (length, val[j].size);
        END (*FOR*);
        lengthV := length;
        INC (length, LengthLength(length));
        INC (length);

        (* Allocate the space. *)

        result.size := length;
        result.allocated := length;
        ALLOCATE (result.data, length);

        (* Fill in the encoding. *)

        result.data^[0] := 17;  pos := 1;        (* code for SEQUENCE *)
        StoreLength (result.data, pos, LengthLength(lengthV)-1, lengthV);
        FOR j := 0 TO N-1 DO
            p := ADR(result.data^[pos]);
            Copy (val[j].data, p, val[j].size);
            INC (pos, val[j].size);
        END (*FOR*);

        RETURN result;

    END ASNEncodeSet;

(************************************************************************)

PROCEDURE ASNEncodeBNArray (N: CARDINAL;  val: ARRAY OF BN): ByteStr;

    (* Produces the ASN.1 encoding of a sequence of N Bignums.  *)

    (* Remark: this can probably be more compactly coded by making use  *)
    (* of ASNEncodeSequence.                                            *)

    (********************************************************************)

    PROCEDURE LengthLength (k: CARDINAL): CARDINAL;

        (* Returns the number of bytes needed to encode length k. *)

        (* Rules: values 0..127 can be encoded in one byte.  For larger *)
        (* k, we need one byte, plus as many bytes as are needed to     *)
        (* represent k in binary.                                       *)

        BEGIN
            IF k < 128 THEN RETURN 1
            ELSIF k < 256 THEN RETURN 2
            ELSIF k < 65536 THEN RETURN 3
            ELSIF k < 16777216 THEN RETURN 4
            ELSE RETURN 5
            END (*IF*);
        END LengthLength;

    (********************************************************************)

    VAR pos: CARDINAL;
        result: ByteStr;

    (********************************************************************)

    PROCEDURE StoreLength2 (LL, k: CARDINAL);

        (* Stores the length code for k at result[pos], updates pos.    *)
        (* LL is the number of extra bytes needed if k > 127.           *)

        BEGIN
            IF LL > 0 THEN
                result.data^[pos] := LL + 80H;  INC(pos);
                IF LL > 1 THEN
                    IF LL > 2 THEN
                        IF LL > 3 THEN
                            result.data^[pos] := k DIV 16777216;  INC(pos);
                            k := k MOD 16777216;
                        END (*IF*);
                        result.data^[pos] := k DIV 65536;  INC(pos);
                        k := k MOD 65536;
                    END (*IF*);
                    result.data^[pos] := k DIV 256;  INC(pos);
                    k := k MOD 256;
                END (*IF*);
            END (*IF*);
            result.data^[pos] := k;  INC(pos);
        END StoreLength2;

    (********************************************************************)

    VAR j, k, lval, length, lengthV: CARDINAL;
        p: ByteStringPtr;
        table: ARRAY [0..63] OF
                    RECORD
                        LL, LV: CARDINAL;
                    END (*RECORD*);

    BEGIN
        (* Start by building a table of lengths.  LL is the length of   *)
        (* the length field, and LV is the length of the value field.   *)
        (* Doing this now will avoid duplicated calculations later.     *)

        FOR j := 0 TO N-1 DO
            lval := BigNum.Nbytes(val[j]);
            table[j].LL := LengthLength (lval);
            table[j].LV := lval;
        END (*FOR*);

        (* Work out the length of the encoding. *)

        length := 0;
        FOR j := 0 TO N-1 DO
            INC (length, table[j].LL + table[j].LV + 1);
        END (*FOR*);
        lengthV := length;
        INC (length, LengthLength(length));
        INC (length);

        (* Allocate the space. *)

        result.size := length;
        result.allocated := length;
        ALLOCATE (result.data, length);

        (* Fill in the encoding. *)

        result.data^[0] := 30H;  pos := 1;        (* code for SEQUENCE *)
        StoreLength2 (LengthLength(lengthV)-1, lengthV);
        FOR j := 0 TO N-1 DO
            result.data^[pos] := 2;  INC (pos);     (* code for INTEGER *)
            StoreLength2 (table[j].LL - 1, table[j].LV);
            p := ADR(result.data^[pos]);
            k := BigNum.BNtoBytes (val[j], p^);
            IF k = table[j].LV THEN
                INC (pos, k);
            ELSE
                WriteString ("ERROR: length error in BigNum.BNtoBytes");
                WriteLn;
            END (*IF*);
        END (*FOR*);

        RETURN result;

    END ASNEncodeBNArray;

(************************************************************************)

END ASN1.

