--- Convert-BER-XS/XS.xs 2019/04/19 16:49:02 1.2 +++ Convert-BER-XS/XS.xs 2019/04/19 19:46:29 1.3 @@ -12,12 +12,32 @@ 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_GENERAL_TIME = 0x18, //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, @@ -35,12 +55,12 @@ ASN_CLASS_SHIFT = 6, // ASN_APPLICATION SNMP - ASN_IPADDRESS = 0x00, - ASN_COUNTER32 = 0x01, - ASN_UNSIGNED32 = 0x02, - ASN_TIMETICKS = 0x03, - ASN_OPAQUE = 0x04, - ASN_COUNTER64 = 0x06, + SNMP_IPADDRESS = 0x00, + SNMP_COUNTER32 = 0x01, + SNMP_UNSIGNED32 = 0x02, + SNMP_TIMETICKS = 0x03, + SNMP_OPAQUE = 0x04, + SNMP_COUNTER64 = 0x06, }; enum { @@ -53,18 +73,29 @@ #define MAX_OID_STRLEN 4096 -static U8 *buf, *cur; -static STRLEN len, rem; +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 (val < 0 || val >= sizeof (cache)) + if (expect_false (val < 0 || val >= sizeof (cache))) return newSViv (val); - if (!cache [val]) + if (expect_false (!cache [val])) { cache [val] = newSVuv (val); SvREADONLY_on (cache [val]); @@ -74,6 +105,7 @@ } ///////////////////////////////////////////////////////////////////////////// +// decoder static void error (const char *errmsg) @@ -81,50 +113,39 @@ croak ("%s at offset 0x%04x", errmsg, cur - buf); } -static int -need (int count) +static void +want (UV count) { - if (count < 0 || (int)rem < count) - { - error ("unexpected end of message buffer"); - return 0; - } - - return 1; + 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 (int count, const U8 *errres) +get_n (UV count) { - if (!need (count)) - return (U8 *)errres; - + want (count); U8 *res = cur; - cur += count; - rem -= count; - return res; } +// get single octet static U8 get_u8 (void) { - if (rem <= 0) - { - error ("unexpected end of message buffer"); - return 0; - } + if (cur == end) + error ("unexpected end of message buffer"); - rem--; return *cur++; } +// get ber-encoded integer (i.e. pack "w") static U32 -get_ber (void) +get_w (void) { U32 res = 0; @@ -179,10 +200,7 @@ return 0; } - U8 *data = get_n (length, 0); - - if (!data) - return 0; + U8 *data = get_n (length); if (length > 5 || (length > 4 && data [0])) { @@ -223,10 +241,7 @@ return 0; } - U8 *data = get_n (length, 0); - - if (!data) - return 0; + U8 *data = get_n (length); if (length > 9 || (length > 8 && data [0])) { @@ -260,14 +275,7 @@ decode_octet_string (void) { U32 length = get_length (); - - U8 *data = get_n (length, 0); - if (!data) - { - error ("OCTET STRING too long"); - return &PL_sv_undef; - } - + U8 *data = get_n (length); return newSVpvn (data, length); } @@ -313,7 +321,7 @@ } U8 *end = cur + length; - U32 w = get_ber (); + U32 w = get_w (); static char oid[MAX_OID_STRLEN]; // must be static char *app = oid; @@ -325,7 +333,7 @@ // we assume an oid component is never > 64 bytes while (cur < end && oid + sizeof (oid) - app > 64) { - w = get_ber (); + w = get_w (); *app++ = '.'; app = write_uv (app, w); } @@ -345,10 +353,10 @@ int tag = identifier & ASN_TAG_MASK; if (tag == ASN_TAG_BER) - tag = get_ber (); + tag = get_w (); if (tag == ASN_TAG_BER) - tag = get_ber (); + tag = get_w (); if (constructed) { @@ -379,9 +387,9 @@ res = decode_integer32 (); break; - case ASN_APPLICATION | ASN_UNSIGNED32: - case ASN_APPLICATION | ASN_COUNTER32: - case ASN_APPLICATION | ASN_TIMETICKS: + case ASN_APPLICATION | SNMP_UNSIGNED32: + case ASN_APPLICATION | SNMP_COUNTER32: + case ASN_APPLICATION | SNMP_TIMETICKS: res = decode_unsigned32 (); break; @@ -393,7 +401,7 @@ break; #endif - case ASN_APPLICATION | ASN_COUNTER64: + case ASN_APPLICATION | SNMP_COUNTER64: res = decode_integer64 (); break; @@ -412,6 +420,325 @@ 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); +} + +static void +encode_ber (SV *tuple) +{ + if (expect_false (!SvROK (tuple) || SvTYPE (SvRV (tuple)) != SVt_PVAV)) + croak ("BER tuple must be array-reference"); + + AV *av = (AV *)SvRV (tuple); + + if (expect_false (SvRMAGICAL (av))) + croak ("BER tuple must not be tied"); + + if (expect_false (AvFILL (av) != BER_ARRAYSIZE - 1)) + croak ("BER tuple must contain exactly %d elements, not %d", BER_ARRAYSIZE, AvFILL (av) + 1); + + 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 @@ -440,12 +767,12 @@ { "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 }, + { "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 }, @@ -461,9 +788,11 @@ ber_decode (SV *ber) CODE: { + STRLEN len; + buf = SvPVbyte (ber, len); cur = buf; - rem = len; + end = buf + len; RETVAL = decode_ber (); } @@ -492,7 +821,6 @@ void ber_is_seq (SV *tuple) - PROTOTYPE: $ PPCODE: { if (!SvOK (tuple)) @@ -512,7 +840,6 @@ void ber_is_i32 (SV *tuple, IV value) - PROTOTYPE: $$ PPCODE: { if (!SvOK (tuple)) @@ -533,7 +860,6 @@ void ber_is_oid (SV *tuple, SV *oid) - PROTOTYPE: $$ PPCODE: { if (!SvOK (tuple)) @@ -552,3 +878,19 @@ ? &PL_sv_yes : &PL_sv_no); } +############################################################################# + +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); +} +