#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_OID = 0x06, //X ASN_OBJECT_DESCRIPTOR = 0x07, //X ASN_EXTERNAL = 0x08, //X ASN_REAL = 0x09, //X ASN_ENUMERATED = 0x0a, //X ASN_EMBEDDED_PDV = 0x0b, //X ASN_UTF8_STRING = 0x0c, //X ASN_RELATIVE_OID = 0x0d, //X ASN_SEQUENCE = 0x10, ASN_SET = 0x11, //X ASN_NUMERIC_STRING = 0x12, //X ASN_PRINTABLE_STRING = 0x13, //X ASN_TELETEX_STRING = 0x14, //X ASN_T61_STRING = 0x14, //X ASN_VIDEOTEX_STRING = 0x15, //X ASN_IA5_STRING = 0x16, //X ASN_ASCII_STRING = 0x16, //X ASN_UTC_TIME = 0x17, //X ASN_GENERALIZED_TIME = 0x18, //X ASN_GRAPHIC_STRING = 0x19, //X ASN_VISIBLE_STRING = 0x1a, //X ASN_ISO646_STRING = 0x1a, //X ASN_GENERAL_STRING = 0x1b, //X ASN_UNIVERSAL_STRING = 0x1c, //X ASN_CHARACTER_STRING = 0x1d, //X ASN_BMPSTRING = 0x1e, //X 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 SNMP SNMP_IPADDRESS = 0x00, SNMP_COUNTER32 = 0x01, SNMP_UNSIGNED32 = 0x02, SNMP_TIMETICKS = 0x03, SNMP_OPAQUE = 0x04, SNMP_COUNTER64 = 0x06, }; enum { BER_CLASS = 0, BER_TAG = 1, BER_CONSTRUCTED = 2, BER_DATA = 3, BER_ARRAYSIZE }; #define MAX_OID_STRLEN 4096 static SV *buf_sv; // encoding buffer static U8 *buf, *cur, *end; // buffer start, current, end #if __GNUC__ >= 3 # define expect(expr,value) __builtin_expect ((expr), (value)) # define INLINE static inline #else # define expect(expr,value) (expr) # define INLINE static #endif #define expect_false(expr) expect ((expr) != 0, 0) #define expect_true(expr) expect ((expr) != 0, 1) // for "small" integers, return a readonly sv, otherwise create a new one static SV *newSVcacheint (int val) { static SV *cache[32]; if (expect_false (val < 0 || val >= sizeof (cache))) return newSViv (val); if (expect_false (!cache [val])) { cache [val] = newSVuv (val); SvREADONLY_on (cache [val]); } return SvREFCNT_inc_NN (cache [val]); } ///////////////////////////////////////////////////////////////////////////// // decoder static void error (const char *errmsg) { croak ("%s at offset 0x%04x", errmsg, cur - buf); } static void want (UV count) { if (expect_false ((uintptr_t)(end - cur) < count)) error ("unexpected end of message buffer"); } // get_* functions fetch something from the buffer // decode_* functions use get_* fun ctions to decode ber values // get n octets static U8 * get_n (UV count) { want (count); U8 *res = cur; cur += count; return res; } // get single octet static U8 get_u8 (void) { if (cur == end) error ("unexpected end of message buffer"); return *cur++; } // get ber-encoded integer (i.e. pack "w") static U32 get_w (void) { U32 res = 0; for (;;) { U8 c = get_u8 (); res = (res << 7) | (c & 0x7f); if (!(c & 0x80)) return res; } } static U32 get_length (void) { U32 res = get_u8 (); 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) | get_u8 (); case 3: res = (res << 8) | get_u8 (); case 2: res = (res << 8) | get_u8 (); case 1: res = (res << 8) | get_u8 (); } } return res; } static U32 get_integer32 (void) { U32 length = get_length (); if (length <= 0) { error ("INTEGER32 length equal to zero"); return 0; } U8 *data = get_n (length); 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 * decode_integer32 (void) { return newSViv ((I32)get_integer32 ()); } static SV * decode_unsigned32 (void) { return newSVuv ((U32)get_integer32 ()); } #if IVSIZE >= 8 static U64TYPE get_integer64 (void) { U32 length = get_length (); if (length <= 0) { error ("INTEGER64 length equal to zero"); return 0; } U8 *data = get_n (length); 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 * decode_integer64 (void) { return newSViv ((I64TYPE)get_integer64 ()); } static SV * decode_unsigned64 (void) { return newSVuv ((U64TYPE)get_integer64 ()); } #endif static SV * decode_octet_string (void) { U32 length = get_length (); U8 *data = get_n (length); return newSVpvn (data, length); } // gelper for decode_object_identifier 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 * decode_object_identifier (void) { U32 length = get_length (); if (length <= 0) { error ("OBJECT IDENTIFIER length equal to zero"); return &PL_sv_undef; } U8 *end = cur + length; U32 w = get_w (); 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 = get_w (); *app++ = '.'; app = write_uv (app, w); } return newSVpvn (oid, app - oid); } static SV * decode_ber () { int identifier = get_u8 (); 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 = get_w (); if (tag == ASN_TAG_BER) tag = get_w (); if (constructed) { U32 len = get_length (); U32 seqend = (cur - buf) + len; AV *av = (AV *)sv_2mortal ((SV *)newAV ()); while (cur < buf + seqend) av_push (av, decode_ber ()); 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 = decode_object_identifier (); break; case ASN_INTEGER32: res = decode_integer32 (); break; case ASN_APPLICATION | SNMP_UNSIGNED32: case ASN_APPLICATION | SNMP_COUNTER32: case ASN_APPLICATION | SNMP_TIMETICKS: res = decode_unsigned32 (); break; #if 0 // handled by default case case ASN_OCTET_STRING: case ASN_APPLICATION | ASN_IPADDRESS: case ASN_APPLICATION | ASN_OPAQUE: res = decode_octet_string (); break; #endif case ASN_APPLICATION | SNMP_COUNTER64: res = decode_integer64 (); break; default: res = decode_octet_string (); 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); } ///////////////////////////////////////////////////////////////////////////// // encoder /* adds two STRLENs together, slow, and with paranoia */ static STRLEN strlen_sum (STRLEN l1, STRLEN l2) { size_t sum = l1 + l2; if (sum < (size_t)l2 || sum != (size_t)(STRLEN)sum) croak ("JSON::XS: string size overflow"); return sum; } static void set_buf (SV *sv) { STRLEN len; buf_sv = sv; buf = SvPVbyte (buf_sv, len); cur = buf; end = buf + len; } /* similar to SvGROW, but somewhat safer and guarantees exponential realloc strategy */ static char * my_sv_grow (SV *sv, size_t len1, size_t len2) { len1 = strlen_sum (len1, len2); len1 = strlen_sum (len1, len1 >> 1); if (len1 > 4096 - 24) len1 = (len1 | 4095) - 24; return SvGROW (sv, len1); } static void need (STRLEN len) { if (expect_false ((uintptr_t)(end - cur) < len)) { STRLEN pos = cur - buf; buf = my_sv_grow (buf_sv, pos, len); cur = buf + pos; end = buf + SvLEN (buf_sv) - 1; } } static void put_u8 (int val) { need (1); *cur++ = val; } static void put_w_nocheck (U32 val) { *cur = (val >> 7 * 4) | 0x80; cur += val >= (1 << (7 * 4)); *cur = (val >> 7 * 3) | 0x80; cur += val >= (1 << (7 * 3)); *cur = (val >> 7 * 2) | 0x80; cur += val >= (1 << (7 * 2)); *cur = (val >> 7 * 1) | 0x80; cur += val >= (1 << (7 * 1)); *cur = val & 0x7f; cur += 1; } static void put_w (U32 val) { need (5); // we only handle up to 5 bytes put_w_nocheck (val); } static U8 * put_length_at (U32 val, U8 *cur) { if (val < 0x7fU) *cur++ = val; else { U8 *lenb = cur++; *cur = val >> 24; cur += *cur > 0; *cur = val >> 16; cur += *cur > 0; *cur = val >> 8; cur += *cur > 0; *cur = val ; cur += 1; *lenb = 0x80 + cur - lenb - 1; } return cur; } static void put_length (U32 val) { need (5); cur = put_length_at (val, cur); } // return how many bytes the encoded length requires static int length_length (U32 val) { return val < 0x7fU ? 1 : 2 + (val > 0xffU) + (val > 0xffffU) + (val > 0xffffffU); } static void encode_octet_string (SV *sv) { STRLEN len; char *ptr = SvPVbyte (sv, len); put_length (len); need (len); memcpy (cur, ptr, len); cur += len; } static void encode_integer32 (IV iv) { need (5); U8 *lenb = cur++; if (iv < 0) { // get two's complement bit pattern - works even on hypthetical non-2c machines U32 uv = iv; *cur = uv >> 24; cur += !!(~uv & 0xff800000U); *cur = uv >> 16; cur += !!(~uv & 0xffff8000U); *cur = uv >> 8; cur += !!(~uv & 0xffffff80U); *cur = uv ; cur += 1; } else { *cur = iv >> 24; cur += *cur > 0; *cur = iv >> 16; cur += *cur > 0; *cur = iv >> 8; cur += *cur > 0; *cur = iv ; cur += 1; } *lenb = cur - lenb - 1; } static void encode_unsigned64 (U64TYPE uv) { need (9); U8 *lenb = cur++; *cur = uv >> 56; cur += *cur > 0; *cur = uv >> 48; cur += *cur > 0; *cur = uv >> 40; cur += *cur > 0; *cur = uv >> 32; cur += *cur > 0; *cur = uv >> 24; cur += *cur > 0; *cur = uv >> 16; cur += *cur > 0; *cur = uv >> 8; cur += *cur > 0; *cur = uv ; cur += 1; *lenb = cur - lenb - 1; } // we don't know the length yet, so we optimistically // assume the length will need one octet later. if that // turns out to be wrong, we memove as needed. // mark the beginning static STRLEN len_fixup_mark () { return cur++ - buf; } // patch up the length static void len_fixup (STRLEN mark) { STRLEN reallen = (cur - buf) - mark - 1; int lenlen = length_length (reallen); if (expect_false (lenlen > 1)) { // bad luck, we have to shift the bytes to make room for the length need (5); memmove (buf + mark + lenlen, buf + mark + 1, reallen); cur += lenlen - 1; } put_length_at (reallen, buf + mark); } static char * read_uv (char *str, UV *uv) { UV r = 0; while (*str >= '0') r = r * 10 + *str++ - '0'; *uv = r; str += !!*str; // advance over any non-zero byte return str; } static void encode_object_identifier (SV *oid) { STRLEN slen; char *ptr = SvPV (oid, slen); // utf8 vs. bytes does not matter // we need at most as many octets as the string form need (slen + 1); STRLEN mark = len_fixup_mark (); UV w1, w2; ptr = read_uv (ptr, &w1); ptr = read_uv (ptr, &w2); put_w_nocheck (w1 * 40 + w2); while (*ptr) { ptr = read_uv (ptr, &w1); put_w_nocheck (w1); } len_fixup (mark); } // checkl whether an SV is a BER tuple and returns its AV * static AV * ber_tuple (SV *tuple) { SV *rv; if (expect_false (!SvROK (tuple) || SvTYPE ((rv = SvRV (tuple))) != SVt_PVAV)) croak ("BER tuple must be array-reference"); if (expect_false (SvRMAGICAL (rv))) croak ("BER tuple must not be tied"); if (expect_false (AvFILL ((AV *)rv) != BER_ARRAYSIZE - 1)) croak ("BER tuple must contain exactly %d elements, not %d", BER_ARRAYSIZE, AvFILL ((AV *)rv) + 1); return (AV *)rv; } static void encode_ber (SV *tuple) { AV *av = ber_tuple (tuple); int klass = SvIV (AvARRAY (av)[BER_CLASS]); int tag = SvIV (AvARRAY (av)[BER_TAG]); int constructed = SvIV (AvARRAY (av)[BER_CONSTRUCTED]) ? ASN_CONSTRUCTED : 0; SV *data = AvARRAY (av)[BER_DATA]; int identifier = (klass << ASN_CLASS_SHIFT) | constructed; if (expect_false (tag >= ASN_TAG_BER)) { put_u8 (identifier | ASN_TAG_BER); put_w (tag); } else put_u8 (identifier | tag); if (constructed) { // we optimistically assume that only one length byte is needed // and adjust later need (1); STRLEN mark = len_fixup_mark (); if (expect_false (!SvROK (data) || SvTYPE (SvRV (data)) != SVt_PVAV)) croak ("BER constructed data must be array-reference"); AV *av = (AV *)SvRV (data); int fill = AvFILL (av); if (expect_false (SvRMAGICAL (av))) croak ("BER constructed data must not be tied"); for (int i = 0; i <= fill; ++i) encode_ber (AvARRAY (av)[i]); len_fixup (mark); } else switch (identifier | tag) { case ASN_NULL: put_length (0); break; case ASN_OBJECT_IDENTIFIER: encode_object_identifier (data); break; case ASN_INTEGER32: encode_integer32 (SvIV (data)); break; case ASN_APPLICATION | SNMP_UNSIGNED32: case ASN_APPLICATION | SNMP_COUNTER32: case ASN_APPLICATION | SNMP_TIMETICKS: case ASN_APPLICATION | SNMP_COUNTER64: encode_unsigned64 (SvUV (data)); break; default: encode_octet_string (data); break; } } ///////////////////////////////////////////////////////////////////////////// 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 }, { "SNMP_IPADDRESS", SNMP_IPADDRESS }, { "SNMP_COUNTER32", SNMP_COUNTER32 }, { "SNMP_UNSIGNED32", SNMP_UNSIGNED32 }, { "SNMP_TIMETICKS", SNMP_TIMETICKS }, { "SNMP_OPAQUE", SNMP_OPAQUE }, { "SNMP_COUNTER64", SNMP_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: { STRLEN len; buf = SvPVbyte (ber, len); cur = buf; end = buf + len; RETVAL = decode_ber (); } 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) PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; if (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV) croak ("ber_is: 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_undef); } void ber_is_seq (SV *tuple) PPCODE: { if (!SvOK (tuple)) XSRETURN_UNDEF; AV *av = ber_tuple (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, SV *value = &PL_sv_undef) PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; AV *av = ber_tuple (tuple); IV data = SvIV (AvARRAY (av)[BER_DATA]); XPUSHs ( SvIV (AvARRAY (av)[BER_CLASS ]) == ASN_UNIVERSAL && SvIV (AvARRAY (av)[BER_TAG ]) == ASN_INTEGER32 && !SvIV (AvARRAY (av)[BER_CONSTRUCTED]) && (!SvOK (value) || data == SvIV (value)) ? sv_2mortal (data ? newSViv (data) : newSVpv ("0 but true", 0)) : &PL_sv_undef); } void ber_is_oid (SV *tuple, SV *oid = &PL_sv_undef) PPCODE: { if (!SvOK (tuple)) XSRETURN_NO; AV *av = ber_tuple (tuple); XPUSHs ( SvIV (AvARRAY (av)[BER_CLASS ]) == ASN_UNIVERSAL && SvIV (AvARRAY (av)[BER_TAG ]) == ASN_OBJECT_IDENTIFIER && !SvIV (AvARRAY (av)[BER_CONSTRUCTED]) && (!SvOK (oid) || sv_eq (AvARRAY (av)[BER_DATA], oid)) ? newSVsv (AvARRAY (av)[BER_DATA]) : &PL_sv_undef); } ############################################################################# void ber_encode (SV *tuple) PPCODE: { buf_sv = sv_2mortal (NEWSV (0, 256)); SvPOK_only (buf_sv); set_buf (buf_sv); encode_ber (tuple); SvCUR_set (buf_sv, cur - buf); XPUSHs (buf_sv); } SV * ber_i32 (IV iv) CODE: { AV *av = newAV (); av_fill (av, BER_ARRAYSIZE - 1); AvARRAY (av)[BER_CLASS ] = newSVcacheint (ASN_UNIVERSAL); AvARRAY (av)[BER_TAG ] = newSVcacheint (ASN_INTEGER32); AvARRAY (av)[BER_CONSTRUCTED] = newSVcacheint (0); AvARRAY (av)[BER_DATA ] = newSViv (iv); RETVAL = newRV_noinc ((SV *)av); } OUTPUT: RETVAL # TODO: not arrayref, but elements? SV * ber_seq (SV *arrayref) CODE: { AV *av = newAV (); av_fill (av, BER_ARRAYSIZE - 1); AvARRAY (av)[BER_CLASS ] = newSVcacheint (ASN_UNIVERSAL); AvARRAY (av)[BER_TAG ] = newSVcacheint (ASN_SEQUENCE); AvARRAY (av)[BER_CONSTRUCTED] = newSVcacheint (1); AvARRAY (av)[BER_DATA ] = newSVsv (arrayref); RETVAL = newRV_noinc ((SV *)av); } OUTPUT: RETVAL