#include "EXTERN.h" #include "perl.h" #include "XSUB.h" // C99 required enum { // ASN_TAG ASN_BOOLEAN = 0x01, ASN_INTEGER32 = 0x02, ASN_BIT_STRING = 0x03, ASN_OCTET_STRING = 0x04, ASN_NULL = 0x05, ASN_OBJECT_IDENTIFIER = 0x06, ASN_SEQUENCE = 0x10, ASN_TAG_BER = 0x1f, ASN_TAG_MASK = 0x1f, // primitive/constructed ASN_CONSTRUCTED = 0x20, // ASN_CLASS ASN_UNIVERSAL = 0x00, ASN_APPLICATION = 0x40, ASN_CONTEXT = 0x80, ASN_PRIVATE = 0xc0, ASN_CLASS_MASK = 0xc0, ASN_CLASS_SHIFT = 6, // ASN_APPLICATION ASN_IPADDRESS = 0x00, ASN_COUNTER32 = 0x01, ASN_UNSIGNED32 = 0x02, ASN_TIMETICKS = 0x03, ASN_OPAQUE = 0x04, ASN_COUNTER64 = 0x06, }; enum { BER_CLASS = 0, BER_TAG = 1, BER_CONSTRUCTED = 2, BER_DATA = 3, BER_ARRAYSIZE }; #define MAX_OID_STRLEN 4096 static U8 *buf, *cur; static STRLEN len, rem; // for "small" integers, return a readonly sv, otherwise create a new one static SV *newSVcacheint (int val) { static SV *cache[32]; if (val < 0 || val >= sizeof (cache)) return newSViv (val); if (!cache [val]) { cache [val] = newSVuv (val); SvREADONLY_on (cache [val]); } return SvREFCNT_inc_NN (cache [val]); } ///////////////////////////////////////////////////////////////////////////// static void error (const char *errmsg) { croak ("%s at offset 0x%04x", errmsg, cur - buf); } static int need (int count) { if (count < 0 || (int)rem < count) { error ("unexpected end of message buffer"); return 0; } return 1; } static U8 * getn (int count, const U8 *errres) { if (!need (count)) return (U8 *)errres; U8 *res = cur; cur += count; rem -= count; return res; } static U8 get8 (void) { if (rem <= 0) { error ("unexpected end of message buffer"); return 0; } rem--; return *cur++; } static U32 getb (void) { U32 res = 0; for (;;) { U8 c = get8 (); res = (res << 7) | (c & 0x7f); if (!(c & 0x80)) return res; } } static U32 process_length (void) { U32 res = get8 (); if (res & 0x80) { int cnt = res & 0x7f; res = 0; switch (cnt) { case 0: error ("indefinite ASN.1 lengths not supported"); return 0; default: error ("ASN.1 length too long"); return 0; case 4: res = (res << 8) | get8 (); case 3: res = (res << 8) | get8 (); case 2: res = (res << 8) | get8 (); case 1: res = (res << 8) | get8 (); } } return res; } static U32 process_integer32 (void) { U32 length = process_length (); if (length <= 0) { error ("INTEGER32 length equal to zero"); return 0; } U8 *data = getn (length, 0); if (!data) return 0; if (length > 5 || (length > 4 && data [0])) { error ("INTEGER32 length too long"); return 0; } U32 res = data [0] & 0x80 ? 0xffffffff : 0; while (length--) res = (res << 8) | *data++; return res; } static SV * process_integer32_sv (void) { return newSViv ((I32)process_integer32 ()); } static SV * process_unsigned32_sv (void) { return newSVuv ((U32)process_integer32 ()); } #if IVSIZE >= 8 static U64TYPE process_integer64 (void) { U32 length = process_length (); if (length <= 0) { error ("INTEGER64 length equal to zero"); return 0; } U8 *data = getn (length, 0); if (!data) return 0; if (length > 9 || (length > 8 && data [0])) { error ("INTEGER64 length too long"); return 0; } U64TYPE res = data [0] & 0x80 ? 0xffffffffffffffff : 0; while (length--) res = (res << 8) | *data++; return res; } static SV * process_integer64_sv (void) { return newSViv ((I64TYPE)process_integer64 ()); } static SV * process_unsigned64_sv (void) { return newSVuv ((U64TYPE)process_integer64 ()); } #endif static SV * process_octet_string_sv (void) { U32 length = process_length (); U8 *data = getn (length, 0); if (!data) { error ("OCTET STRING too long"); return &PL_sv_undef; } return newSVpvn (data, length); } static char * write_uv (char *buf, U32 u) { // the one-digit case is absolutely predominant, so this pays off (hopefully) if (u < 10) *buf++ = u + '0'; else { char *beg = buf; do { *buf++ = u % 10 + '0'; u /= 10; } while (u); // reverse digits for (char *ptr = buf; --ptr != beg; ++beg) { char c = *ptr; *ptr = *beg; *beg = c; } } return buf; } static SV * process_object_identifier_sv (void) { U32 length = process_length (); if (length <= 0) { error ("OBJECT IDENTIFIER length equal to zero"); return &PL_sv_undef; } U8 *end = cur + length; U32 w = getb (); static char oid[MAX_OID_STRLEN]; // must be static char *app = oid; app = write_uv (app, (U8)w / 40); *app++ = '.'; app = write_uv (app, (U8)w % 40); // we assume an oid component is never > 64 bytes while (cur < end && oid + sizeof (oid) - app > 64) { w = getb (); *app++ = '.'; app = write_uv (app, w); } return newSVpvn (oid, app - oid); } static SV * ber_decode () { int identifier = get8 (); SV *res; int constructed = identifier & ASN_CONSTRUCTED; int klass = identifier & ASN_CLASS_MASK; int tag = identifier & ASN_TAG_MASK; if (tag == ASN_TAG_BER) tag = getb (); if (tag == ASN_TAG_BER) tag = getb (); if (constructed) { U32 len = process_length (); U32 seqend = (cur - buf) + len; AV *av = (AV *)sv_2mortal ((SV *)newAV ()); while (cur < buf + seqend) av_push (av, ber_decode ()); if (cur > buf + seqend) croak ("constructed type %02x overflow (%x %x)\n", identifier, cur - buf, seqend); res = newRV_inc ((SV *)av); } else switch (identifier) { case ASN_NULL: res = &PL_sv_undef; break; case ASN_OBJECT_IDENTIFIER: res = process_object_identifier_sv (); break; case ASN_INTEGER32: res = process_integer32_sv (); break; case ASN_APPLICATION | ASN_UNSIGNED32: case ASN_APPLICATION | ASN_COUNTER32: case ASN_APPLICATION | ASN_TIMETICKS: res = process_unsigned32_sv (); break; #if 0 // handled by default case case ASN_OCTET_STRING: case ASN_APPLICATION | ASN_IPADDRESS: case ASN_APPLICATION | ASN_OPAQUE: res = process_octet_string_sv (); break; #endif case ASN_APPLICATION | ASN_COUNTER64: res = process_integer64_sv (); break; default: res = process_octet_string_sv (); break; } AV *av = newAV (); av_fill (av, BER_ARRAYSIZE - 1); AvARRAY (av)[BER_CLASS ] = newSVcacheint (klass >> ASN_CLASS_SHIFT); AvARRAY (av)[BER_TAG ] = newSVcacheint (tag); AvARRAY (av)[BER_CONSTRUCTED] = newSVcacheint (constructed ? 1 : 0); AvARRAY (av)[BER_DATA ] = res; return newRV_noinc ((SV *)av); } MODULE = Convert::BER::XS PACKAGE = Convert::BER::XS PROTOTYPES: ENABLE BOOT: { HV *stash = gv_stashpv ("Convert::BER::XS", 1); static const struct { const char *name; IV iv; } *civ, const_iv[] = { { "ASN_BOOLEAN", ASN_BOOLEAN }, { "ASN_INTEGER32", ASN_INTEGER32 }, { "ASN_BIT_STRING", ASN_BIT_STRING }, { "ASN_OCTET_STRING", ASN_OCTET_STRING }, { "ASN_NULL", ASN_NULL }, { "ASN_OBJECT_IDENTIFIER", ASN_OBJECT_IDENTIFIER }, { "ASN_TAG_BER", ASN_TAG_BER }, { "ASN_TAG_MASK", ASN_TAG_MASK }, { "ASN_CONSTRUCTED", ASN_CONSTRUCTED }, { "ASN_UNIVERSAL", ASN_UNIVERSAL >> ASN_CLASS_SHIFT }, { "ASN_APPLICATION", ASN_APPLICATION >> ASN_CLASS_SHIFT }, { "ASN_CONTEXT", ASN_CONTEXT >> ASN_CLASS_SHIFT }, { "ASN_PRIVATE", ASN_PRIVATE >> ASN_CLASS_SHIFT }, { "ASN_CLASS_MASK", ASN_CLASS_MASK }, { "ASN_CLASS_SHIFT", ASN_CLASS_SHIFT }, { "ASN_SEQUENCE", ASN_SEQUENCE }, { "ASN_IPADDRESS", ASN_IPADDRESS }, { "ASN_COUNTER32", ASN_COUNTER32 }, { "ASN_UNSIGNED32", ASN_UNSIGNED32 }, { "ASN_TIMETICKS", ASN_TIMETICKS }, { "ASN_OPAQUE", ASN_OPAQUE }, { "ASN_COUNTER64", ASN_COUNTER64 }, { "BER_CLASS" , BER_CLASS }, { "BER_TAG" , BER_TAG }, { "BER_CONSTRUCTED", BER_CONSTRUCTED }, { "BER_DATA" , BER_DATA }, }; for (civ = const_iv + sizeof (const_iv) / sizeof (const_iv [0]); civ > const_iv; civ--) newCONSTSUB (stash, (char *)civ[-1].name, newSViv (civ[-1].iv)); } SV * ber_decode (SV *ber) CODE: { buf = SvPVbyte (ber, len); cur = buf; rem = len; RETVAL = ber_decode (); } OUTPUT: RETVAL void ber_is (SV *tuple, SV *klass = &PL_sv_undef, SV *tag = &PL_sv_undef, SV *constructed = &PL_sv_undef, SV *data = &PL_sv_undef) PROTOTYPE: $;$$$ PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; if (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV) croak ("ber_seq: tuple must be ber tuple (array-ref)"); AV *av = (AV *)SvRV (tuple); XPUSHs ( (!SvOK (klass) || SvIV (AvARRAY (av)[BER_CLASS ]) == SvIV (klass)) && (!SvOK (tag) || SvIV (AvARRAY (av)[BER_TAG ]) == SvIV (tag)) && (!SvOK (constructed) || !SvIV (AvARRAY (av)[BER_CONSTRUCTED]) == !SvIV (constructed)) && (!SvOK (data) || sv_eq (AvARRAY (av)[BER_DATA ], data)) ? &PL_sv_yes : &PL_sv_no); } void ber_is_seq (SV *tuple) PROTOTYPE: $ PPCODE: { if (!SvOK (tuple)) XSRETURN_UNDEF; if (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV) croak ("ber_seq: tuple must be ber tuple (array-ref)"); AV *av = (AV *)SvRV (tuple); XPUSHs ( SvIV (AvARRAY (av)[BER_CLASS ]) == ASN_UNIVERSAL && SvIV (AvARRAY (av)[BER_TAG ]) == ASN_SEQUENCE && SvIV (AvARRAY (av)[BER_CONSTRUCTED]) ? AvARRAY (av)[BER_DATA] : &PL_sv_undef); } void ber_is_i32 (SV *tuple, IV value) PROTOTYPE: $$ PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; if (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV) croak ("ber_seq: tuple must be ber tuple (array-ref)"); AV *av = (AV *)SvRV (tuple); XPUSHs ( SvIV (AvARRAY (av)[BER_CLASS ]) == ASN_UNIVERSAL && SvIV (AvARRAY (av)[BER_TAG ]) == ASN_INTEGER32 && !SvIV (AvARRAY (av)[BER_CONSTRUCTED]) && SvIV (AvARRAY (av)[BER_DATA ]) == value ? &PL_sv_yes : &PL_sv_no); } void ber_is_oid (SV *tuple, SV *oid) PROTOTYPE: $$ PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; if (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV) croak ("ber_seq: tuple must be ber tuple (array-ref)"); AV *av = (AV *)SvRV (tuple); XPUSHs ( SvIV (AvARRAY (av)[BER_CLASS ]) == ASN_UNIVERSAL && SvIV (AvARRAY (av)[BER_TAG ]) == ASN_OBJECT_IDENTIFIER && !SvIV (AvARRAY (av)[BER_CONSTRUCTED]) && sv_eq (AvARRAY (av)[BER_DATA], oid) ? &PL_sv_yes : &PL_sv_no); }