summaryrefslogtreecommitdiff
path: root/appl/lib/asn1.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/lib/asn1.b')
-rw-r--r--appl/lib/asn1.b1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/appl/lib/asn1.b b/appl/lib/asn1.b
new file mode 100644
index 00000000..0ccb7d26
--- /dev/null
+++ b/appl/lib/asn1.b
@@ -0,0 +1,1030 @@
+implement ASN1;
+
+include "sys.m";
+ sys: Sys;
+
+include "asn1.m";
+
+# Masks
+TAG_MASK : con 16r1F;
+CONSTR_MASK : con 16r20;
+CLASS_MASK : con 16rC0;
+
+# Decoding errors
+OK, ESHORT, ETOOBIG, EVALLEN, ECONSTR, EPRIM, EINVAL, EUNIMPL: con iota;
+
+debug : con 0;
+
+init()
+{
+ sys = load Sys Sys->PATH;
+}
+
+# Decode the whole array as a BER encoding of an ASN1 type.
+# If there's an error, the return string will contain the error.
+# Depending on the error, the returned elem may or may not
+# be nil.
+decode(a: array of byte) : (string, ref Elem)
+{
+ (ecode, i, elem) := ber_decode(a, 0, len a);
+ return (errstr(ecode, i, len a), elem);
+}
+
+# Like decode, but continue decoding after first element
+# of array ends.
+decode_seq(a: array of byte) : (string, list of ref Elem)
+{
+ (ecode, i, elist) := seq_decode(a, 0, len a, -1, 1);
+ return (errstr(ecode, i, len a), elist);
+}
+
+# Decode the whole array as a BER encoding of an ASN1 value,
+# (i.e., the part after the tag and length).
+# Assume the value is encoded as universal tag "kind".
+# The constr arg is 1 if the value is constructed, 0 if primitive.
+# If there's an error, the return string will contain the error.
+# Depending on the error, the returned value may or may not
+# be nil.
+decode_value(a: array of byte, kind, constr: int) : (string, ref Value)
+{
+ n := len a;
+ (ecode, i, val) := value_decode(a, 0, n, n, kind, constr);
+ return (errstr(ecode, i, len a), val);
+}
+
+# The rest of the decoding routines take the array (a), the
+# starting position (i), and the ending position +1 (n).
+# They return (err code, new i, [... varies]).
+
+# for debugging
+ber_ind := "";
+ber_ind_save := "";
+
+# Decode an ASN1 (tag, length, value).
+ber_decode(a: array of byte, i, n: int) : (int, int, ref Elem)
+{
+ if(debug) {
+ ber_ind_save = ber_ind;
+ ber_ind = ber_ind + " ";
+ sys->print("%sber_decode, byte %d\n", ber_ind, i);
+ }
+ err, length: int;
+ tag : Tag;
+ val : ref Value;
+ elem : ref Elem = nil;
+ (err, i, tag) = tag_decode(a, i, n);
+ if(err == OK) {
+ (err, i, length) = length_decode(a, i, n);
+ if(err == OK) {
+ if(debug)
+ sys->print("%sgot tag %s, length %d, now at byte %d\n",
+ ber_ind, tag.tostring(), length, i);
+ if(tag.class == Universal)
+ (err, i, val) = value_decode(a, i, n, length, tag.num, tag.constr);
+ else
+ (err, i, val) = value_decode(a, i, n, length, OCTET_STRING, 0);
+ if(val != nil)
+ elem = ref Elem(tag, val);
+ }
+ }
+ if(debug) {
+ sys->print("%send ber_decode, byte %d\n", ber_ind, i);
+ if(val != nil) {
+ sys->print("%sdecode result:\n", ber_ind);
+ print_elem(elem);
+ }
+ if(err != OK)
+ sys->print("%serror: %s\n", ber_ind, errstr(err, i, i));
+ ber_ind = ber_ind_save;
+ }
+ return (err, i, elem);
+}
+
+# Decode a tag field. As well as Tag, return an int that
+# is 1 if the type is constructed, 0 if not.
+tag_decode(a: array of byte, i, n: int) : (int, int, Tag)
+{
+ err := OK;
+ class, num, constr: int;
+ if(n-i >= 2) {
+ v := int a[i++];
+ class = v & CLASS_MASK;
+ if(v & CONSTR_MASK)
+ constr = 1;
+ else
+ constr = 0;
+ num = v & TAG_MASK;
+ if(num == TAG_MASK)
+ # long tag number
+ (err, i, num) = uint7_decode(a, i, n);
+ }
+ else
+ err = ESHORT;
+ return (err, i, Tag(class, num, constr));
+}
+
+# Decode a length field. Assume it fits in a Limbo int.
+# If "indefinite length", return -1.
+length_decode(a: array of byte, i, n: int) : (int, int, int)
+{
+ err := OK;
+ num := 0;
+ if(i < n) {
+ v := int a[i++];
+ if(v & 16r80)
+ return int_decode(a, i, n, v&16r7F, 1);
+ else if(v == 16r80)
+ num = -1;
+ else
+ num = v;
+ }
+ else
+ err = ESHORT;
+ return (err, i, num);
+}
+
+# Decode a value according to the encoding of the Universal
+# type with number "kind" and constructed/primitive according
+# to "constr", with given length (may be -1, for "indefinite").
+value_decode(a: array of byte, i, n, length, kind, constr: int) : (int, int, ref Value)
+{
+ err := OK;
+ val : ref Value;
+ va : array of byte;
+ if(length == -1) {
+ if(!constr)
+ err = EINVAL;
+ }
+ else if(i+length > n)
+ err = EVALLEN;
+ if(err != OK)
+ return (err, i, nil);
+ case kind {
+ 0 =>
+ # marker for end of indefinite constructions
+ if(length == 0)
+ val = ref Value.EOC;
+ else
+ err = EINVAL;
+ BOOLEAN =>
+ if(constr)
+ err = ECONSTR;
+ else if(length != 1)
+ err = EVALLEN;
+ else {
+ val = ref Value.Bool(int a[0]);
+ i++;
+ }
+ INTEGER or ENUMERATED =>
+ if(constr)
+ err = ECONSTR;
+ else if(length <= 4) {
+ num : int;
+ (err, i, num) = int_decode(a, i, i+length, length, 0);
+ if(err == OK)
+ val = ref Value.Int(num);
+ }
+ else {
+ va = array[length] of byte;
+ va[0:] = a[i:i+length];
+ val = ref Value.BigInt(va);
+ i += length;
+ }
+ BIT_STRING =>
+ if(constr) {
+ if(length == -1 && i+2 <= n && a[i] == byte 0 && a[i+1] == byte 0) {
+ val = ref Value.BitString(0, nil);
+ i += 2;
+ }
+ else
+ # TODO: recurse and concat results
+ err = EUNIMPL;
+ }
+ else {
+ if(length < 2) {
+ if(length == 1 && a[0] == byte 0) {
+ val = ref Value.BitString(0, nil);
+ i ++;
+ }
+ else
+ err = EINVAL;
+ }
+ else {
+ bitsunused := int a[i];
+ if(bitsunused > 7)
+ err = EINVAL;
+ else if(length > 16r0FFFFFFF)
+ err = ETOOBIG;
+ else {
+ va = array[length-1] of byte;
+ va[0:] = a[i+1:i+length];
+ val = ref Value.BitString(bitsunused, va);
+ i += length;
+ }
+ }
+ }
+ OCTET_STRING or ObjectDescriptor =>
+ (err, i, va) = octet_decode(a, i, n, length, constr);
+ if(err == OK)
+ val = ref Value.Octets(va);
+ NULL =>
+ if(constr)
+ err = ECONSTR;
+ else if(length != 0)
+ err = EVALLEN;
+ else
+ val = ref Value.Null;
+ OBJECT_ID =>
+ if(constr)
+ err = ECONSTR;
+ else if (length == 0)
+ err = EVALLEN;
+ else {
+ subids : list of int = nil;
+ iend := i+length;
+ while(i < iend) {
+ x : int;
+ (err, i, x) = uint7_decode(a, i, n);
+ if(err != OK)
+ break;
+ subids = x :: subids;
+ }
+ if(err == OK) {
+ if(i != iend)
+ err = EVALLEN;
+ else {
+ m := len subids;
+ ia := array[m+1] of int;
+ while(subids != nil) {
+ y := hd subids;
+ subids = tl subids;
+ if(m == 1) {
+ ia[1] = y % 40;
+ ia[0] = y / 40;
+ }
+ else
+ ia[m--] = y;
+ }
+ val = ref Value.ObjId(ref Oid(ia));
+ }
+ }
+ }
+ EXTERNAL or EMBEDDED_PDV =>
+ # TODO: parse this internally
+ va = array[length] of byte;
+ va[0:] = a[i:i+length];
+ val = ref Value.Other(va);
+ i += length;
+ REAL =>
+ # let the appl decode, with math module
+ if(constr)
+ err = ECONSTR;
+ else {
+ va = array[length] of byte;
+ va[0:] = a[i:i+length];
+ val = ref Value.Real(va);
+ i += length;
+ }
+ SEQUENCE or SET=>
+ vl : list of ref Elem;
+ (err, i, vl) = seq_decode(a, i, n, length, constr);
+ if(err == OK) {
+ if(kind == SEQUENCE)
+ val = ref Value.Seq(vl);
+ else
+ val = ref Value.Set(vl);
+ }
+ NumericString or PrintableString or TeletexString
+ or VideotexString or IA5String or UTCTime
+ or GeneralizedTime or GraphicString or VisibleString
+ or GeneralString or UniversalString or BMPString =>
+ (err, i, va) = octet_decode(a, i, n, length, constr);
+ if(err == OK)
+ # sometimes wrong: need to do char set conversion
+ val = ref Value.String(string va);
+
+ * =>
+ va = array[length] of byte;
+ va[0:] = a[i:i+length];
+ val = ref Value.Other(va);
+ i += length;
+ }
+ return (err, i, val);
+}
+
+# Decode an int in format where count bytes are
+# concatenated to form value.
+# Although ASN1 allows any size integer, we return
+# an error if the result doesn't fit in a Limbo int.
+# If unsigned is not set, make sure to propagate sign bit.
+int_decode(a: array of byte, i, n, count, unsigned: int) : (int, int, int)
+{
+ err := OK;
+ num := 0;
+ if(n-i >= count) {
+ if((count > 4) || (unsigned && count == 4 && (int a[i] & 16r80)))
+ err = ETOOBIG;
+ else {
+ if(!unsigned && count > 0 && count < 4 && (int a[i] & 16r80))
+ num = -1; # all bits set
+ for(j := 0; j < count; j++) {
+ v := int a[i++];
+ num = (num << 8) | v;
+ }
+ }
+ }
+ else
+ err = ESHORT;
+ return (err, i, num);
+}
+
+# Decode an unsigned int in format where each
+# byte except last has high bit set, and remaining
+# seven bits of each byte are concatenated to form value.
+# Although ASN1 allows any size integer, we return
+# an error if the result doesn't fit in a Limbo int.
+uint7_decode(a: array of byte, i, n: int) : (int, int, int)
+{
+ err := OK;
+ num := 0;
+ more := 1;
+ while(more && i < n) {
+ v := int a[i++];
+ if(num & 16r7F000000) {
+ err = ETOOBIG;
+ break;
+ }
+ num <<= 7;
+ more = v & 16r80;
+ num |= (v & 16r7F);
+ }
+ if(n == i)
+ err = ESHORT;
+ return (err, i, num);
+}
+
+# Decode an octet string, recursively if constr.
+# We've already checked that length==-1 implies constr==1,
+# and otherwise that specified length fits within a[i..n].
+octet_decode(a: array of byte, i, n, length, constr: int) : (int, int, array of byte)
+{
+ err := OK;
+ va : array of byte;
+ if(length >= 0 && !constr) {
+ va = array[length] of byte;
+ va[0:] = a[i:i+length];
+ i += length;
+ }
+ else {
+ # constructed, either definite or indefinite length
+ lva : list of array of byte = nil;
+ elem : ref Elem;
+ istart := i;
+ totbytes := 0;
+ cloop:
+ for(;;) {
+ if(length >= 0 && i >= istart+length) {
+ if(i != istart+length)
+ err = EVALLEN;
+ break cloop;
+ }
+ oldi := i;
+ (err, i, elem) = ber_decode(a, i, n);
+ if(err != OK)
+ break;
+ pick v := elem.val {
+ Octets =>
+ lva = v.bytes :: lva;
+ totbytes += len v.bytes;
+ EOC =>
+ if(length != -1) {
+ i = oldi;
+ err = EINVAL;
+ }
+ break cloop;
+ * =>
+ i = oldi;
+ err = EINVAL;
+ break cloop;
+ }
+ }
+ if(err == OK) {
+ va = array[totbytes] of byte;
+ j := totbytes;
+ while(lva != nil) {
+ x := hd lva;
+ lva = tl lva;
+ m := len x;
+ va[j-m:] = x[0:];
+ j -= m;
+ }
+ }
+ }
+ return (err, i, va);
+}
+
+# Decode a sequence or set.
+# We've already checked that length==-1 implies constr==1,
+# and otherwise that specified length fits within a[i..n].
+seq_decode(a : array of byte, i, n, length, constr: int) : (int, int, list of ref Elem)
+{
+ err := OK;
+ ans : list of ref Elem = nil;
+ if(!constr)
+ err = EPRIM;
+ else {
+ # constructed, either definite or indefinite length
+ lve : list of ref Elem = nil;
+ elem : ref Elem;
+ istart := i;
+ cloop:
+ for(;;) {
+ if(length >= 0 && i >= istart+length) {
+ if(i != istart+length)
+ err = EVALLEN;
+ break cloop;
+ }
+ oldi := i;
+ (err, i, elem) = ber_decode(a, i, n);
+ if(err != OK)
+ break;
+ pick v := elem.val {
+ EOC =>
+ if(length != -1) {
+ i = oldi;
+ err = EINVAL;
+ }
+ break cloop;
+ * =>
+ lve = elem :: lve;
+ }
+ }
+ if(err == OK) {
+ # reverse back to original order
+ while(lve != nil) {
+ e := hd lve;
+ lve = tl lve;
+ ans = e :: ans;
+ }
+ }
+ }
+ return (err, i, ans);
+}
+
+# Encode e by BER rules
+encode(e: ref Elem) : (string, array of byte)
+{
+ (err, n) := enc(nil, e, 0, 1);
+ if(err != "")
+ return (err, nil);
+ b := array[n] of byte;
+ enc(b, e, 0, 0);
+ return ("", b);
+}
+
+# Encode e into array b, only putting in bytes if !lenonly.
+# Start at loc i, return index after.
+enc(b: array of byte, e: ref Elem, i, lenonly: int) : (string, int)
+{
+ (err, vlen, constr) := val_enc(b, e, 0, 1);
+ if(err != "")
+ return (err, i);
+ tag := e.tag;
+ v := tag.class | constr;
+ if(tag.num < 31) {
+ if(!lenonly)
+ b[i] = byte (v | tag.num);
+ i++;
+ }
+ else {
+ if(!lenonly)
+ b[i] = byte (v | 31);
+ if(tag.num < 0)
+ return ("negative tag number", i);
+ i = uint7_enc(b, tag.num, i+1, lenonly);
+ }
+ if(vlen < 16r80) {
+ if(!lenonly)
+ b[i] = byte vlen;
+ i++;
+ }
+ else {
+ ilen := int_enc(b, vlen, 1, 0, 1);
+ if(!lenonly) {
+ b[i] = byte (16r80 | ilen);
+ i = int_enc(b, vlen, 1, i+1, 0);
+ }
+ else
+ i += 1+ilen;
+ }
+ if(!lenonly)
+ val_enc(b, e, i, 0);
+ i += vlen;
+ return ("", i);
+}
+
+# Encode e.val into array b, only putting in bytes if !lenonly.
+# Start at loc i, return (err, index after, constructed or primitive)
+val_enc(b: array of byte, e: ref Elem, i, lenonly: int) : (string, int, int)
+{
+ kind := e.tag.num;
+ cl := e.tag.class;
+ ok := 1;
+ v : int;
+ bb : array of byte;
+ constr := 0;
+ if(cl != Universal) {
+ pick vv := e.val {
+ Bool =>
+ kind = BOOLEAN;
+ Int =>
+ kind = INTEGER;
+ BigInt =>
+ kind = INTEGER;
+ Octets =>
+ kind = OCTET_STRING;
+ Real =>
+ kind = REAL;
+ Other =>
+ kind = OCTET_STRING;
+ BitString =>
+ kind = BIT_STRING;
+ Null =>
+ kind = NULL;
+ ObjId =>
+ kind = OBJECT_ID;
+ String =>
+ kind = UniversalString;
+ Seq =>
+ kind = SEQUENCE;
+ Set =>
+ kind = SET;
+ }
+ }
+ case kind {
+ BOOLEAN =>
+ (ok, v) = e.is_int();
+ if(ok) {
+ if(v != 0)
+ v = 255;
+ i = int_enc(b, v, 1, i, lenonly);
+ }
+ INTEGER or ENUMERATED =>
+ (ok, v) = e.is_int();
+ if(ok)
+ i = int_enc(b, v, 0, i, lenonly);
+ else {
+ (ok, bb) = e.is_bigint();
+ if(ok) {
+ if(!lenonly)
+ b[i:] = bb;
+ i += len bb;
+ }
+ }
+ BIT_STRING =>
+ (ok, v, bb) = e.is_bitstring();
+ if(ok) {
+ if(bb == nil) {
+ if(!lenonly)
+ b[i] = byte 0;
+ i++;
+ }
+ else {
+ if(v < 0 || v > 7)
+ ok = 0;
+ else {
+ if(!lenonly) {
+ b[i] = byte v;
+ b[i+1:] = bb;
+ }
+ i += 1 + len bb;
+ }
+ }
+ }
+ OCTET_STRING or ObjectDescriptor or EXTERNAL or REAL
+ or EMBEDDED_PDV =>
+ pick vv := e.val {
+ Octets or Real or Other =>
+ if(!lenonly && vv.bytes != nil)
+ b[i:] = vv.bytes;
+ i += len vv.bytes;
+ * =>
+ ok = 0;
+ }
+ NULL =>
+ ;
+ OBJECT_ID =>
+ oid : ref Oid;
+ (ok, oid) = e.is_oid();
+ if(ok) {
+ n := len oid.nums;
+ for(k := 0; k < n; k++) {
+ v = oid.nums[k];
+ if(k == 0) {
+ v *= 40;
+ if(n > 1)
+ v += oid.nums[++k];
+ }
+ i = uint7_enc(b, v, i, lenonly);
+ }
+ }
+ SEQUENCE or SET =>
+ pick vv := e.val {
+ Seq or Set =>
+ constr = CONSTR_MASK;
+ for(l := vv.l; l != nil; l = tl l) {
+ err : string;
+ (err, i) = enc(b, hd l, i, lenonly);
+ if(err != "")
+ return (err, i, 0);
+ }
+ }
+ NumericString or PrintableString or TeletexString
+ or VideotexString or IA5String or UTCTime
+ or GeneralizedTime or GraphicString or VisibleString
+ or GeneralString or UniversalString or BMPString =>
+ pick vv := e.val {
+ String =>
+ bb = array of byte vv.s;
+ if(!lenonly && bb != nil)
+ b[i:] = bb;
+ i += len bb;
+ * =>
+ ok = 0;
+ }
+ * =>
+ ok = 0;
+ }
+ if(!ok)
+ return ("bad value for encoding kind", i, constr);
+ return ("", i, constr);
+}
+
+# Encode num as unsigned 7 bit values with top bit 1 on all bytes
+# except last, into array b, only putting in bytes if !lenonly.
+# Start at loc i, return index after.
+uint7_enc(b: array of byte, num, i, lenonly: int) : int
+{
+ n := 1;
+ v := num>>7;
+ while(v > 0) {
+ v >>= 7;
+ n++;
+ }
+ if(lenonly)
+ i += n;
+ else {
+ for(k := (n-1)*7; k > 0; k -= 7)
+ b[i++] = byte ((num>>k) | 16r80);
+ b[i++] = byte (num & 16r7F);
+ }
+ return i;
+}
+
+# Encode num as unsigned or signed integer into array b,
+# only putting in bytes if !lenonly.
+# Encoding is length followed by bytes to concatenate.
+# Start at loc i, return index after.
+int_enc(b: array of byte, num, unsigned, i, lenonly: int) : int
+{
+ v := num;
+ if(v < 0)
+ v = -(v+1);
+ n := 1;
+ prevv := v;
+ v >>= 8;
+ while(v > 0) {
+ prevv = v;
+ v >>= 8;
+ n++;
+ }
+ if(!unsigned && (prevv & 16r80))
+ n++;
+ if(lenonly)
+ i += n;
+ else {
+ for(k := (n-1)*8; k >= 0; k -= 8)
+ b[i++] = byte (num>>k);
+ }
+ return i;
+}
+
+# Compare two arrays of integers; return true if they match
+intarr_eq(a: array of int, b: array of int) : int
+{
+ alen := len a;
+ if(alen != len b)
+ return 0;
+ for(i := 0; i < alen; i++)
+ if(a[i] != b[i])
+ return 0;
+ return 1;
+}
+
+# Look for o in tab; if found, return index, else return -1.
+oid_lookup(o: ref Oid, tab: array of Oid) : int
+{
+ for(i := 0; i < len tab; i++)
+ if(intarr_eq(o.nums, tab[i].nums))
+ return i;
+ return -1;
+}
+
+# If e is a SEQUENCE, return (1, e's element list)
+# else return (error, nil).
+Elem.is_seq(e: self ref Elem) : (int, list of ref Elem)
+{
+ if(e.tag.class == Universal && e.tag.num == SEQUENCE) {
+ pick v := e.val {
+ Seq =>
+ return (1, v.l);
+ }
+ }
+ return (0, nil);
+}
+
+# If e is a SET, return (1, e's element list)
+# else return (error, nil).
+Elem.is_set(e: self ref Elem) : (int, list of ref Elem)
+{
+ if(e.tag.class == Universal && e.tag.num == SET) {
+ pick v := e.val {
+ Set =>
+ return (1, v.l);
+ }
+ }
+ return (0, nil);
+}
+
+# If e is an INTEGER that fits in a limbo int, return (1, val)
+# else return (0, 0l).
+Elem.is_int(e: self ref Elem) : (int, int)
+{
+ if(e.tag.class == Universal && (e.tag.num == INTEGER || e.tag.num == BOOLEAN)) {
+ pick v := e.val {
+ Bool or
+ Int =>
+ return (1, v.v);
+ }
+ }
+ return (0, 0);
+}
+
+# If e is an INTEGER that doesn't fit in a limbo int, return (1, bytes),
+# or even if it does fit, return it as an array of bytes.
+# else return (0, nil).
+Elem.is_bigint(e: self ref Elem) : (int, array of byte)
+{
+ if(e.tag.class == Universal && e.tag.num == INTEGER) {
+ pick v := e.val {
+ BigInt =>
+ return (1, v.bytes);
+ Int =>
+ x := v.v;
+ a := array[4] of byte;
+ for(i := 0; i < 4; i++)
+ a[i] = byte ((x >> (8*(3-i))) & 16rFF);
+ for(j := 0; j < 3; j++)
+ if(a[j] != byte 0)
+ break;
+ return (1, a[j:]);
+ }
+ }
+ return (0, nil);
+}
+
+# If e is a bitstring, return (1, unused bits, bytes containing bit string),
+# else return (0, nil)
+Elem.is_bitstring(e: self ref Elem) : (int, int, array of byte)
+{
+ if(e.tag.class == Universal && e.tag.num == BIT_STRING) {
+ pick v := e.val {
+ BitString =>
+ return (1, v.unusedbits, v.bits);
+ }
+ }
+ return (0, 0, nil);
+}
+
+# If e is an octetstring, return (1, bytes),
+# else return (0, nil)
+Elem.is_octetstring(e: self ref Elem) : (int, array of byte)
+{
+ if(e.tag.class == Universal && e.tag.num == OCTET_STRING) {
+ pick v := e.val {
+ Octets =>
+ return (1, v.bytes);
+ }
+ }
+ return (0, nil);
+}
+
+# If e is an object id, return (1, ref Oid),
+# else return (0, nil)
+Elem.is_oid(e: self ref Elem) : (int, ref Oid)
+{
+ if(e.tag.class == Universal && e.tag.num == OBJECT_ID) {
+ pick v := e.val {
+ ObjId =>
+ return (1, v.id);
+ }
+ }
+ return (0, nil);
+}
+
+# If e is some kind of string (excluding times), return (1, string),
+# else return (0, "")
+Elem.is_string(e: self ref Elem) : (int, string)
+{
+ if(e.tag.class == Universal) {
+ case e.tag.num {
+ NumericString or PrintableString or TeletexString
+ or VideotexString or IA5String or GraphicString
+ or VisibleString or GeneralString or UniversalString
+ or BMPString =>
+ pick v := e.val {
+ String =>
+ return (1, v.s);
+ }
+ }
+ }
+ return (0, nil);
+}
+
+# If e is some kind of time, return (1, string),
+# else return (0, "")
+Elem.is_time(e: self ref Elem) : (int, string)
+{
+ if(e.tag.class == Universal
+ && (e.tag.num == UTCTime || e.tag.num == GeneralizedTime)) {
+ pick v := e.val {
+ String =>
+ return (1, v.s);
+ }
+ }
+ return (0, nil);
+}
+
+# Return printable error string for code ecode.
+# i is position where error is first noted.
+# n is the end of the passed data: if i!=n then
+# we didn't use all the data and an error should
+# be returned about that.
+errstr(ecode, i, n: int) : string
+{
+ if(ecode == OK && i == n)
+ return "";
+ err := "BER decode: ";
+ case ecode {
+ OK =>
+ err += "OK";
+ ESHORT =>
+ err += "need more data";
+ ETOOBIG =>
+ err += "value exceeds implementation limit";
+ EVALLEN =>
+ err += "value has wrong length";
+ ECONSTR =>
+ err += "value is constructed, should be primitive";
+ EPRIM =>
+ err += "value is primitive";
+ EINVAL =>
+ err += "value encoding invalid";
+ * =>
+ err += "unknown error " + string ecode;
+ }
+ if(err == "" && i != n)
+ err += "extra data";
+ err += " at byte " + string i;
+ return err;
+}
+
+# Printing functions, for debugging
+
+Tag.tostring(t: self Tag) : string
+{
+ ans := "";
+ snum := string t.num;
+ if(t.class == Universal) {
+ case t.num {
+ BOOLEAN => ans = "BOOLEAN";
+ INTEGER => ans = "INTEGER";
+ BIT_STRING => ans = "BIT STRING";
+ OCTET_STRING => ans = "OCTET STRING";
+ NULL => ans = "NULL";
+ OBJECT_ID => ans = "OBJECT IDENTIFER";
+ ObjectDescriptor => ans = "OBJECT_DES";
+ EXTERNAL => ans = "EXTERNAL";
+ REAL => ans = "REAL";
+ ENUMERATED => ans = "ENUMERATED";
+ EMBEDDED_PDV => ans = "EMBEDDED PDV";
+ SEQUENCE => ans = "SEQUENCE";
+ SET => ans = "SET";
+ NumericString => ans = "NumericString";
+ PrintableString => ans = "PrintableString";
+ TeletexString => ans = "TeletexString";
+ VideotexString => ans = "VideotexString";
+ IA5String => ans = "IA5String";
+ UTCTime => ans = "UTCTime";
+ GeneralizedTime => ans = "GeneralizedTime";
+ GraphicString => ans = "GraphicString";
+ VisibleString => ans = "VisibleString";
+ GeneralString => ans = "GeneralString";
+ UniversalString => ans = "UniversalString";
+ BMPString => ans = "BMPString";
+ * => ans = "UNIVERSAL " + snum;
+ }
+ }
+ else {
+ case t.class {
+ Application =>
+ ans = "APPLICATION " + snum;
+ Context =>
+ ans = "CONTEXT "+ snum;
+ Private =>
+ ans = "PRIVATE " + snum;
+ }
+ }
+ return ans;
+}
+
+Elem.tostring(e: self ref Elem) : string
+{
+ return estring(e, "");
+}
+
+Value.tostring(v: self ref Value) : string
+{
+ return vstring(v, "");
+}
+
+estring(e: ref Elem, indent: string) : string
+{
+ return indent + e.tag.tostring() + " " + vstring(e.val, indent);
+}
+
+vstring(val: ref Value, indent: string) : string
+{
+ ans := "";
+ pick v := val {
+ Bool or Int =>
+ ans += string v.v;
+ Octets or BigInt or Real or Other =>
+ ans += bastring(v.bytes, indent + "\t");
+ BitString =>
+ ans += " bits (unused " +string v.unusedbits + ")" + bastring(v.bits, indent + "\t");
+ Null or EOC =>
+ ;
+ ObjId =>
+ ans += v.id.tostring();
+ String =>
+ ans += "\"" + v.s + "\"";
+ Seq or Set =>
+ ans += "{\n";
+ newindent := indent + "\t";
+ l := v.l;
+ while(l != nil) {
+ if(ans[len ans-1] != '\n')
+ ans[len ans] = '\n';
+ ans += estring(hd l, newindent);
+ l = tl l;
+ }
+ if(ans[len ans-1] != '\n')
+ ans[len ans] = '\n';
+ ans += indent + "}";
+ }
+ return ans;
+}
+
+bastring(a: array of byte, indent: string) : string
+{
+ if(sys == nil)
+ sys = load Sys Sys->PATH;
+ ans := indent;
+ nlindent := "\n" + indent;
+ for(i := 0; i < len a; i++) {
+ if(i < len a - 1 && i%10 == 0)
+ ans += nlindent ;
+ ans += sys->sprint("%2x ", int a[i]);
+ }
+ return ans;
+}
+
+Oid.tostring(o: self ref Oid) : string
+{
+ ans := "";
+ for(i := 0; i < len o.nums; i++) {
+ ans += string o.nums[i];
+ if(i < len o.nums - 1)
+ ans[len ans] = '.';
+ }
+ return ans;
+}
+
+print_elem(e: ref Elem)
+{
+ s := e.tostring();
+ a := array of byte s;
+ sys->write(sys->fildes(1), a, len a);
+ sys->print("\n");
+}