#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
// C99 required
//#define BENCHMARK
#define ASN_BOOLEAN 0x01
#define ASN_INTEGER32 0x02
#define ASN_OCTET_STRING 0x04
#define ASN_NULL 0x05
#define ASN_OBJECT_IDENTIFIER 0x06
#define ASN_SEQUENCE 0x30
#define ASN_IPADDRESS 0x40
#define ASN_COUNTER32 0x41
#define ASN_UNSIGNED32 0x42
#define ASN_TIMETICKS 0x43
#define ASN_OPAQUE 0x44
#define ASN_COUNTER64 0x46
#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
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;
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
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;
}
}
#ifdef BENCHMARK
static double t1;
static double
tstamp (void)
{
struct timeval tv;
gettimeofday (&tv, 0);
return tv.tv_sec + tv.tv_usec * 0.000001;
}
#endif
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
if (u < 10)
*buf++ = u + '0';
else
buf += sprintf (buf, "%u", (unsigned int)u);
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;
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 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_UNSIGNED32:
case ASN_COUNTER32:
case ASN_TIMETICKS:
res = process_unsigned32_sv ();
break;
case ASN_SEQUENCE:
res = newSVuv (process_length ());
break;
case ASN_OCTET_STRING:
case ASN_OPAQUE:
res = process_octet_string_sv ();
break;
default:
{
if (type > AvFILLp (av_type) || SvTYPE (AvARRAY (av_type)[type]) != SVt_PVCV)
{
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);
}
#endif
MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::XS
PROTOTYPES: ENABLE
BOOT:
av_type = newAV ();
void
set_type (int type, SV *cv)
CODE:
av_store (av_type, type, SvREFCNT_inc (x_get_cv (cv)));
MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::Message
void
_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.
if (count < 0)
{
hv_delete ((HV *)SvRV (self), "_index" , 6, G_DISCARD);
hv_delete ((HV *)SvRV (self), "_length", 7, G_DISCARD);
XPUSHs (sv_2mortal (newSVsv (bufsv)));
sv_setpvn (bufsv, "", 0);
buf = "";
cur = buf;
rem = 0;
XSRETURN (1);
}
char *data = getn (count, 0);
if (data)
XPUSHs (sv_2mortal (newSVpvn (data, count)));
}
U32
index (BUFOBJ self, int ndx = -1)
CODE:
{
if (ndx >= 0 && ndx < len)
{
cur = buf + ndx;
rem = len - ndx;
}
RETVAL = cur - buf;
}
OUTPUT:
RETVAL
U32
_process_length (BUFOBJ self, ...)
ALIAS:
_process_sequence = 0
CODE:
RETVAL = process_length ();
OUTPUT:
RETVAL
SV *
_process_integer32 (BUFOBJ self, ...)
CODE:
RETVAL = process_integer32_sv ();
OUTPUT:
RETVAL
SV *
_process_counter (BUFOBJ self, ...)
ALIAS:
_process_gauge = 0
_process_timeticks = 0
CODE:
RETVAL = process_unsigned32_sv ();
OUTPUT:
RETVAL
#if IVSIZE >= 8
SV *
_process_counter64 (BUFOBJ self, ...)
CODE:
RETVAL = process_unsigned64_sv ();
OUTPUT:
RETVAL
#endif
SV *
_process_object_identifier (BUFOBJ self, ...)
CODE:
RETVAL = process_object_identifier_sv ();
OUTPUT:
RETVAL
SV *
_process_octet_string (BUFOBJ self, ...)
ALIAS:
_process_opaque = 0
CODE:
RETVAL = process_octet_string_sv ();
OUTPUT:
RETVAL
SV *
_process_ipaddress (BUFOBJ self, ...)
CODE:
{
U32 length = process_length ();
if (length != 4)
{
error ("IP ADDRESS length not four");
XSRETURN_UNDEF;
}
U8 *data = getn (4, "\x00\x00\x00\x00");
RETVAL = newSVpvf ("%d.%d.%d.%d", data [0], data [1], data [2], data [3]);
}
OUTPUT:
RETVAL
SV *
process (BUFOBJ self, SV *expected = &PL_sv_undef, SV *found = 0)
CODE:
{
int type;
RETVAL = process_sv (&type);
if (found)
sv_setiv (found, type);
if (SvOK (expected) && type != SvIV (expected))
error ("Expected a different type than found");
}
OUTPUT:
RETVAL
MODULE = Net::SNMP::XS PACKAGE = Net::SNMP::PDU
SV *
_process_var_bind_list (BUFOBJ self)
CODE:
{
if (get8 () != ASN_SEQUENCE)
error ("SEQUENCE expected at beginning of VarBindList");
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)
{
// SEQUENCE ObjectName ObjectSyntax
if (get8 () != ASN_SEQUENCE)
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);
}
// sigh - great design to do it here
SV *pdu_type = *hv_fetch ((HV *)cur_bufobj, "_pdu_type" , sizeof ("_pdu_type" ) - 1, 1);
if (SvIV (pdu_type) == 0xa8) // REPORT
{
PUSHMARK (SP);
XPUSHs (msg);
PUTBACK;
call_method ("_report_pdu_error", G_VOID | G_DISCARD);
SPAGAIN;
XSRETURN_EMPTY;
}
RETVAL = newRV_inc ((SV *)list);
}
OUTPUT:
RETVAL
MODULE = Net::SNMP::XS PACKAGE = Net::SNMP
void
oid_base_match (SV *base_, SV *oid_)
PROTOTYPE: $$
ALIAS:
oid_context_match = 0
PPCODE:
{
if (!SvOK (base_) || !SvOK (oid_))
XSRETURN_NO;
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;
if (memcmp (base, oid, blen))
XSRETURN_NO;
if (oid [blen] && oid [blen] != '.')
XSRETURN_NO;
XSRETURN_YES;
}
#if HAVE_VERSIONSORT
void
oid_lex_sort (...)
PROTOTYPE: @
PPCODE:
{
// make sure SvPVX is valid
int i;
for (i = items; i--; )
{
SV *sv = ST (i);
if (SvTYPE (sv) < SVt_PV || SvTYPE (sv) == SVt_PVAV && SvTYPE (sv) == SVt_PVHV)
SvPV_force_nolen (sv);
}
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))));
}
int
_index_cmp (const char *a, const char *b)
PROTOTYPE: $$
CODE:
RETVAL = strverscmp (a, b);
OUTPUT:
RETVAL
#endif