--- Net-SNMP-XS/XS.xs 2009/04/08 10:39:32 1.2 +++ Net-SNMP-XS/XS.xs 2019/04/22 11:13:16 1.23 @@ -4,40 +4,134 @@ // C99 required -#define ASN_BOOLEAN 0x01 -#define ASN_INTEGER 0x02 -#define ASN_BIT_STR 0x03 -#define ASN_OCTET_STR 0x04 -#define ASN_NULL 0x05 -#define ASN_OBJECT_ID 0x06 -#define ASN_SEQUENCE 0x10 -#define ASN_SET 0x11 - -#define ASN_UNIVERSAL 0x00 -#define ASN_APPLICATION 0x40 -#define ASN_CONTEXT 0x80 -#define ASN_PRIVATE 0xc0 - -#define ASN_PRIMITIVE 0x00 -#define ASN_CONSTRUCTOR 0x20 - -#define ASN_LONG_LEN 0x80 -#define ASN_EXTENSION_ID 0x1f -#define ASN_BIT8 0x80 - //#define BENCHMARK -static SV *msg; -static int errflag; +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, +}; + +#define MAX_OID_STRLEN 4096 + +#define HAVE_VERSIONSORT defined (_GNU_SOURCE) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 + +static SV *cur_bufobj; +static SV *msg, *bufsv; +static int errflag, leading_dot; static U8 *buf, *cur; static STRLEN len, rem; +typedef SV *BUFOBJ; + +///////////////////////////////////////////////////////////////////////////// + +#if 0 + if (msg) + croak ("recursive invocation of Net::SNMP::XS parser is not supported"); + + +void +clr_msg () + CODE: + SvREFCNT_dec (msg); msg = 0; + buf = cur = (U8 *)""; + len = rem = 0; +#endif + static void -error (const char *msg) +clear_bufobj (void) +{ + // serialise our state back + if (msg && SvROK (msg)) + { + SV *idx_sv = *hv_fetch ((HV *)cur_bufobj, "_index" , sizeof ("_index" ) - 1, 1); + sv_setiv (idx_sv, cur - buf); + } + + SvREFCNT_dec (msg); + msg = 0; + cur_bufobj = 0; +} + +static void +switch_bufobj (BUFOBJ neu) +{ + clear_bufobj (); + + msg = newSVsv (neu); + cur_bufobj = SvRV (msg); + sv_rvweaken (msg); + + errflag = 0; + leading_dot = -1; + + IV index = SvIV (*hv_fetch ((HV *)cur_bufobj, "_index" , sizeof ("_index" ) - 1, 1)); + bufsv = *hv_fetch ((HV *)cur_bufobj, "_buffer", sizeof ("_buffer") - 1, 1); + + buf = SvPVbyte (bufsv, len); + cur = buf + index; + rem = len - index; +} + +///////////////////////////////////////////////////////////////////////////// + +static SV * +x_get_cv (SV *cb_sv) +{ + HV *st; + GV *gvp; + CV *cv = sv_2cv (cb_sv, &st, &gvp, 0); + + if (!cv) + croak ("CODE reference expected"); + + return (SV *)cv; +} + +static void +error (const char *errmsg) { errflag = 1; - printf ("<<<%s>>>\n", msg);//D + if (!msg) + croak ("Net::SNMP::XS fatal error, parser called without parsing context"); + + dSP; + PUSHMARK (SP); + EXTEND (SP, 2); + PUSHs (msg); + PUSHs (sv_2mortal (newSVpv (errmsg, 0))); + PUTBACK; + call_method ("_error", G_VOID | G_DISCARD); } static int @@ -136,34 +230,309 @@ return res; } -MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::XS +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 > 8 + !data [0]) + { + error ("INTEGER64 length too long"); + return 0; + } + + U64TYPE res = data [0] & 0x80 ? -1 : 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 + { + // this *could* be done much faster using branchless fixed-point arithmetics + char *beg = buf; + + do + { + *buf++ = u % 10 + '0'; + u /= 10; + } + while (u); + + // reverse digits + char *ptr = buf; + while (--ptr > beg) + { + char c = *ptr; + *ptr = *beg; + *beg = c; + ++beg; + } + } + + 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; + + if (leading_dot < 0) + leading_dot = SvTRUE (*hv_fetch ((HV *)SvRV (msg), "_leading_dot", sizeof ("_leading_dot") - 1, 1)); + + *app = '.'; app += ! ! leading_dot; + + { + UV w1, w2; + + if (w < 2 * 40) + (w1 = w / 40), (w2 = w % 40); + else + (w1 = 2), (w2 = w - 2 * 40); + + app = write_uv (app, w1); + *app++ = '.'; + app = write_uv (app, w2); + } + + // 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 AV *av_type; + +static SV * +process_sv (int *found) +{ + int type = get8 (); + + *found = type; + + SV *res; + + switch (type) + { + 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; + + case ASN_SEQUENCE | ASN_CONSTRUCTED: + res = newSVuv (process_length ()); + break; + + case ASN_OCTET_STRING: + case ASN_APPLICATION | ASN_OPAQUE: + res = process_octet_string_sv (); + break; + + default: + { + if (type > AvFILLp (av_type) + || AvARRAY (av_type)[type] == 0 + || AvARRAY (av_type)[type] == &PL_sv_undef) + { + error ("Unknown ASN.1 type"); + return &PL_sv_undef; + } + + dSP; + PUSHMARK (SP); + EXTEND (SP, 2); + PUSHs (msg); + PUSHs (sv_2mortal (newSViv (type))); + PUTBACK; + int count = call_sv (AvARRAY (av_type)[type], G_SCALAR); + SPAGAIN; + res = count ? SvREFCNT_inc (TOPs) : &PL_sv_undef; + } + } + + return errflag ? &PL_sv_undef : res; +} + +///////////////////////////////////////////////////////////////////////////// + +#if HAVE_VERSIONSORT + +static int +oid_lex_cmp (const void *a_, const void *b_) +{ + const char *a = SvPVX (*(SV **)a_); + const char *b = SvPVX (*(SV **)b_); + + a += *a == '.'; + b += *b == '.'; + + return strverscmp (a, b); +} -void -set_msg (SV *msg_, SV *buf_) - CODE: - errflag = 0; - msg = SvREFCNT_inc (msg_); - buf = SvPVbyte (buf_, len); - cur = buf; - rem = len; -#ifdef BENCHMARK - t1 = tstamp (); #endif +MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::XS + +PROTOTYPES: ENABLE + +BOOT: + av_type = newAV (); + void -clr_msg () +set_type (int type, SV *cv) CODE: - SvREFCNT_dec (msg); - buf = cur = ""; - len = rem = 0; -#ifdef BENCHMARK - printf ("%f\n", tstamp () - t1);//D -#endif + cv = x_get_cv (cv); + assert (SvTYPE (cv) == SVt_PVCV); + av_store (av_type, type, SvREFCNT_inc_NN (cv)); MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::Message void -_buffer_get (SV *self, int count = -1) +_buffer_append (BUFOBJ self, SV *value) + ALIAS: + _buffer_put = 1 + PPCODE: +{ + STRLEN vlen; + const char *vstr = SvPVbyte (value, vlen); + + if (ix) + sv_insert (bufsv, 0, 0, vstr, vlen); + else + sv_catpvn (bufsv, vstr, vlen); + + buf = SvPVbyte (bufsv, len); + cur = buf; + rem = len; + + SV *len_sv = *hv_fetch ((HV *)cur_bufobj, "_length", sizeof ("_length") - 1, 1); + sv_setiv (len_sv, len); + + // some callers test for defined'ness of the returnvalue. *sigh* + XPUSHs (&PL_sv_yes); +} + +void +_buffer_get (BUFOBJ self, int count = -1) PPCODE: { // grrr. @@ -171,9 +540,13 @@ { hv_delete ((HV *)SvRV (self), "_index" , 6, G_DISCARD); hv_delete ((HV *)SvRV (self), "_length", 7, G_DISCARD); - SV **svp = hv_fetch ((HV *)SvRV (self), "_buffer", 7, 1); - XPUSHs (sv_2mortal (newSVsv (*svp))); - sv_setpvn (*svp, "", 0); + XPUSHs (sv_2mortal (newSVsv (bufsv))); + sv_setpvn (bufsv, "", 0); + + buf = ""; + cur = buf; + rem = 0; + XSRETURN (1); } @@ -184,7 +557,7 @@ } U32 -index (SV *self, int ndx = -1) +index (BUFOBJ self, int ndx = -1) CODE: { if (ndx >= 0 && ndx < len) @@ -195,116 +568,204 @@ RETVAL = cur - buf; } - OUTPUT: - RETVAL + OUTPUT: RETVAL U32 -_process_length (SV *self, ...) +_process_length (BUFOBJ self, ...) ALIAS: - _process_sequence = 1 + _process_sequence = 0 CODE: RETVAL = process_length (); - OUTPUT: - RETVAL + OUTPUT: RETVAL -I32 -_process_integer32 (SV *self, ...) +SV * +_process_integer32 (BUFOBJ self, ...) + CODE: + RETVAL = process_integer32_sv (); + OUTPUT: RETVAL + +SV * +_process_counter (BUFOBJ self, ...) ALIAS: - _process_counter = 0 - _process_gauge = 0 + _process_gauge = 0 + _process_timeticks = 0 CODE: -{ - U32 length = process_length (); + RETVAL = process_unsigned32_sv (); + OUTPUT: RETVAL - if (length <= 0) - { - error ("INTEGER32 length equal to zero"); - XSRETURN_UNDEF; - } +#if IVSIZE >= 8 + +SV * +_process_counter64 (BUFOBJ self, ...) + CODE: + RETVAL = process_unsigned64_sv (); + OUTPUT: RETVAL + +#endif - U8 *data = getn (length, 0); +SV * +_process_object_identifier (BUFOBJ self, ...) + CODE: + RETVAL = process_object_identifier_sv (); + OUTPUT: RETVAL - if (!data) - XSRETURN_UNDEF; +SV * +_process_octet_string (BUFOBJ self, ...) + ALIAS: + _process_opaque = 0 + CODE: + RETVAL = process_octet_string_sv (); + OUTPUT: RETVAL - if (length > 5 || (length > 4 && data [0])) +SV * +_process_ipaddress (BUFOBJ self, ...) + CODE: +{ + U32 length = process_length (); + if (length != 4) { - error ("INTEGER32 length too long"); + error ("IP ADDRESS length not four"); XSRETURN_UNDEF; } - U32 res = data [0] & 0x80 ? 0xffffffff : 0; + U8 *data = getn (4, "\x00\x00\x00\x00"); + RETVAL = newSVpvf ("%d.%d.%d.%d", data [0], data [1], data [2], data [3]); +} + OUTPUT: RETVAL - while (length--) - res = (res << 8) | *data++; +SV * +process (BUFOBJ self, SV *expected = &PL_sv_undef, SV *found = 0) + CODE: +{ + int type; + + RETVAL = process_sv (&type); - RETVAL = res; + if (found) + sv_setiv (found, type); + + if (SvOK (expected) && type != SvIV (expected)) + error ("Expected a different type than found"); } - OUTPUT: - RETVAL + OUTPUT: RETVAL + +MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::PDU SV * -_process_object_identifier (SV *self, ...) - CODE: +_process_var_bind_list (BUFOBJ self) + CODE: { - U32 length = process_length (); + if (get8 () != ASN_SEQUENCE | ASN_CONSTRUCTED) + error ("SEQUENCE expected at beginning of VarBindList"); - if (length <= 0) + int seqlen = process_length (); + U8 *end = cur + seqlen; + + HV *list = newHV (); + AV *names = newAV (); + HV *types = newHV (); + + hv_store ((HV *)cur_bufobj, "_var_bind_list" , sizeof ("_var_bind_list" ) - 1, newRV_noinc ((SV *)list ), 0); + hv_store ((HV *)cur_bufobj, "_var_bind_names", sizeof ("_var_bind_names") - 1, newRV_noinc ((SV *)names), 0); + hv_store ((HV *)cur_bufobj, "_var_bind_types", sizeof ("_var_bind_types") - 1, newRV_noinc ((SV *)types), 0); + + while (cur < end && !errflag) { - error ("OBJECT IDENTIFIER length equal to zero"); - XSRETURN_UNDEF; + // SEQUENCE ObjectName ObjectSyntax + if (get8 () != ASN_SEQUENCE | ASN_CONSTRUCTED) + error ("SEQUENCE expected at beginning of VarBind"); + process_length (); + + if (get8 () != ASN_OBJECT_IDENTIFIER) + error ("OBJECT IDENTIFIER expected at beginning of VarBind"); + int type, oidlen; + SV *oid = process_object_identifier_sv (); + SV *val = process_sv (&type); + + hv_store_ent (types, oid, newSViv (type), 0); + hv_store_ent (list , oid, val, 0); + av_push (names, oid); } - - U8 *end = cur + length; - U32 w = getb (); - //TODO: leading_dots - - RETVAL = newSVpvf (".%d.%d", (int)w / 40, (int)w % 40); + // sigh - great design to do it here + SV *pdu_type = *hv_fetch ((HV *)cur_bufobj, "_pdu_type" , sizeof ("_pdu_type" ) - 1, 1); - while (cur < end) + if (SvIV (pdu_type) == 0xa8) // REPORT { - w = getb (); - sv_catpvf (RETVAL, ".%u", (unsigned int)w); + PUSHMARK (SP); + XPUSHs (msg); + PUTBACK; + call_method ("_report_pdu_error", G_VOID | G_DISCARD); + SPAGAIN; + XSRETURN_EMPTY; } + + RETVAL = newRV_inc ((SV *)list); } - OUTPUT: - RETVAL + OUTPUT: RETVAL -SV * -_process_octet_string (SV *self, ...) - ALIAS: - _process_opaque = 0 - CODE: +MODULE = Net::SNMP::XS PACKAGE = Net::SNMP + +void +oid_base_match (SV *base_, SV *oid_) + PROTOTYPE: $$ + ALIAS: + oid_context_match = 0 + PPCODE: { - U32 length = process_length (); + if (!SvOK (base_) || !SvOK (oid_)) + XSRETURN_NO; - U8 *data = getn (length, 0); - if (!data) - { - error ("OCTET STRING too long"); - XSRETURN_UNDEF; - } + STRLEN blen, olen; + char *base = SvPVbyte (base_, blen); + char *oid = SvPVbyte (oid_ , olen); + + blen -= *base == '.'; base += *base == '.'; + olen -= *base == '.'; oid += *oid == '.'; + + if (olen < blen) + XSRETURN_NO; - RETVAL = newSVpvn (data, length); + if (memcmp (base, oid, blen)) + XSRETURN_NO; + + if (oid [blen] && oid [blen] != '.') + XSRETURN_NO; + + XSRETURN_YES; } - OUTPUT: - RETVAL -SV * -_process_ipaddress (SV *self, ...) - CODE: -{ - U32 length = process_length (); - if (length != 4) +#if HAVE_VERSIONSORT + +void +oid_lex_sort (...) + PROTOTYPE: @ + PPCODE: +{ + // make sure SvPVX is valid + int i; + for (i = items; i--; ) { - error ("IP ADDRESS length not four"); - XSRETURN_UNDEF; + SV *sv = ST (i); + + if (SvTYPE (sv) < SVt_PV || SvTYPE (sv) == SVt_PVAV && SvTYPE (sv) == SVt_PVHV) + SvPV_force_nolen (sv); } - U8 *data = getn (4, "\x00\x00\x00\x00"); - RETVAL = newSVpvf ("%d.%d.%d.%d", data [0], data [1], data [2], data [3]); + qsort (&ST (0), items, sizeof (SV *), oid_lex_cmp); + + EXTEND (SP, items); + // we cheat somewhat by not returning copies here + for (i = 0; i < items; ++i) + PUSHs (sv_2mortal (SvREFCNT_inc (ST (i)))); } - OUTPUT: - RETVAL + +int +_index_cmp (const char *a, const char *b) + PROTOTYPE: $$ + CODE: + RETVAL = strverscmp (a, b); + OUTPUT: RETVAL + +#endif