]> arthur.barton.de Git - netdata.git/blob - node.d/node_modules/net-snmp.js
updated net-snmp version
[netdata.git] / node.d / node_modules / net-snmp.js
1
2 // Copyright 2013 Stephen Vickers <stephen.vickers.sv@gmail.com>
3
4 var ber = require ("asn1").Ber;
5 var dgram = require ("dgram");
6 var events = require ("events");
7 var util = require ("util");
8
9 /*****************************************************************************
10  ** Constants
11  **/
12
13 function _expandConstantObject (object) {
14         var keys = [];
15         for (key in object)
16                 keys.push (key);
17         for (var i = 0; i < keys.length; i++)
18                 object[object[keys[i]]] = parseInt (keys[i]);
19 }
20
21 var ErrorStatus = {
22         0: "NoError",
23         1: "TooBig",
24         2: "NoSuchName",
25         3: "BadValue",
26         4: "ReadOnly",
27         5: "GeneralError",
28         6: "NoAccess",
29         7: "WrongType",
30         8: "WrongLength",
31         9: "WrongEncoding",
32         10: "WrongValue",
33         11: "NoCreation",
34         12: "InconsistentValue",
35         13: "ResourceUnavailable",
36         14: "CommitFailed",
37         15: "UndoFailed",
38         16: "AuthorizationError",
39         17: "NotWritable",
40         18: "InconsistentName"
41 };
42
43 _expandConstantObject (ErrorStatus);
44
45 var ObjectType = {
46         1: "Boolean",
47         2: "Integer",
48         4: "OctetString",
49         5: "Null",
50         6: "OID",
51         64: "IpAddress",
52         65: "Counter",
53         66: "Gauge",
54         67: "TimeTicks",
55         68: "Opaque",
56         70: "Counter64",
57         128: "NoSuchObject",
58         129: "NoSuchInstance",
59         130: "EndOfMibView"
60 };
61
62 _expandConstantObject (ObjectType);
63
64 ObjectType.Integer32 = ObjectType.Integer;
65 ObjectType.Counter32 = ObjectType.Counter;
66 ObjectType.Gauge32 = ObjectType.Gauge;
67 ObjectType.Unsigned32 = ObjectType.Gauge32;
68
69 var PduType = {
70         160: "GetRequest",
71         161: "GetNextRequest",
72         162: "GetResponse",
73         163: "SetRequest",
74         164: "Trap",
75         165: "GetBulkRequest",
76         166: "InformRequest",
77         167: "TrapV2",
78         168: "Report"
79 };
80
81 _expandConstantObject (PduType);
82
83 var TrapType = {
84         0: "ColdStart",
85         1: "WarmStart",
86         2: "LinkDown",
87         3: "LinkUp",
88         4: "AuthenticationFailure",
89         5: "EgpNeighborLoss",
90         6: "EnterpriseSpecific"
91 };
92
93 _expandConstantObject (TrapType);
94
95 var Version1 = 0;
96 var Version2c = 1;
97
98 /*****************************************************************************
99  ** Exception class definitions
100  **/
101
102 function ResponseInvalidError (message) {
103         this.name = "ResponseInvalidError";
104         this.message = message;
105         Error.captureStackTrace(this, ResponseInvalidError);
106 }
107 util.inherits (ResponseInvalidError, Error);
108
109 function RequestInvalidError (message) {
110         this.name = "RequestInvalidError";
111         this.message = message;
112         Error.captureStackTrace(this, RequestInvalidError);
113 }
114 util.inherits (RequestInvalidError, Error);
115
116 function RequestFailedError (message, status) {
117         this.name = "RequestFailedError";
118         this.message = message;
119         this.status = status;
120         Error.captureStackTrace(this, RequestFailedError);
121 }
122 util.inherits (RequestFailedError, Error);
123
124 function RequestTimedOutError (message) {
125         this.name = "RequestTimedOutError";
126         this.message = message;
127         Error.captureStackTrace(this, RequestTimedOutError);
128 }
129 util.inherits (RequestTimedOutError, Error);
130
131 /*****************************************************************************
132  ** OID and varbind helper functions
133  **/
134
135 function isVarbindError (varbind) {
136         if (varbind.type == ObjectType.NoSuchObject
137                         || varbind.type == ObjectType.NoSuchInstance
138                         || varbind.type == ObjectType.EndOfMibView)
139                 return true;
140         else
141                 return false;
142 }
143
144 function varbindError (varbind) {
145         return (ObjectType[varbind.type] || "NotAnError") + ": " + varbind.oid;
146 }
147
148 function oidFollowsOid (oidString, nextString) {
149         var oid = {str: oidString, len: oidString.length, idx: 0};
150         var next = {str: nextString, len: nextString.length, idx: 0};
151         var dotCharCode = ".".charCodeAt (0);
152
153         function getNumber (item) {
154                 var n = 0;
155                 if (item.idx >= item.len)
156                         return null;
157                 while (item.idx < item.len) {
158                         var charCode = item.str.charCodeAt (item.idx++);
159                         if (charCode == dotCharCode)
160                                 return n;
161                         n = (n ? (n * 10) : n) + (charCode - 48);
162                 }
163                 return n;
164         }
165
166         while (1) {
167                 var oidNumber = getNumber (oid);
168                 var nextNumber = getNumber (next);
169
170                 if (oidNumber !== null) {
171                         if (nextNumber !== null) {
172                                 if (nextNumber > oidNumber) {
173                                         return true;
174                                 } else if (nextNumber < oidNumber) {
175                                         return false;
176                                 }
177                         } else {
178                                 return true;
179                         }
180                 } else {
181                         return true;
182                 }
183         }
184 }
185
186 function oidInSubtree (oidString, nextString) {
187         var oid = oidString.split (".");
188         var next = nextString.split (".");
189
190         if (oid.length > next.length)
191                 return false;
192
193         for (var i = 0; i < oid.length; i++) {
194                 if (next[i] != oid[i])
195                         return false;
196         }
197
198         return true;
199 }
200
201 /**
202  ** Some SNMP agents produce integers on the wire such as 00 ff ff ff ff.
203  ** The ASN.1 BER parser we use throws an error when parsing this, which we
204  ** believe is correct.  So, we decided not to bother the "asn1" developer(s)
205  ** with this, instead opting to work around it here.
206  **
207  ** If an integer is 5 bytes in length we check if the first byte is 0, and if so
208  ** simply drop it and parse it like it was a 4 byte integer, otherwise throw
209  ** an error since the integer is too large.
210  **/
211
212 function readInt (buffer) {
213         return readUint (buffer, true);
214 }
215
216 function readUint (buffer, isSigned) {
217         buffer.readByte ();
218         var length = buffer.readByte ();
219
220         if (length > 5) {
221                  throw new RangeError ("Integer too long '" + length + "'");
222         } else if (length == 5) {
223                 if (buffer.readByte () !== 0)
224                         throw new RangeError ("Integer too long '" + length + "'");
225                 length = 4;
226         }
227
228         value = 0, signedBitSet = false;
229         
230         for (var i = 0; i < length; i++) {
231                 value *= 256;
232                 value += buffer.readByte ();
233
234                 if (isSigned && i <= 0) {
235                         if ((value & 0x80) == 0x80)
236                                 signedBitSet = true;
237                 }
238         }
239         
240         if (signedBitSet)
241                 value -= (1 << (i * 8));
242
243         return value;
244 }
245
246 function readUint64 (buffer) {
247         var value = buffer.readString (ObjectType.Counter64, true);
248
249         if (value.length > 8)
250                 throw new RequestInvalidError ("64 bit unsigned integer too long '"
251                                 + value.length + "'")
252
253         return value;
254 }
255
256 function readVarbinds (buffer, varbinds) {
257         buffer.readSequence ();
258
259         while (1) {
260                 buffer.readSequence ();
261                 var oid = buffer.readOID ();
262                 var type = buffer.peek ();
263
264                 if (type == null)
265                         break;
266
267                 var value;
268
269                 if (type == ObjectType.Boolean) {
270                         value = buffer.readBoolean ();
271                 } else if (type == ObjectType.Integer) {
272                         value = readInt (buffer);
273                 } else if (type == ObjectType.OctetString) {
274                         value = buffer.readString (null, true);
275                 } else if (type == ObjectType.Null) {
276                         buffer.readByte ();
277                         buffer.readByte ();
278                         value = null;
279                 } else if (type == ObjectType.OID) {
280                         value = buffer.readOID ();
281                 } else if (type == ObjectType.IpAddress) {
282                         var bytes = buffer.readString (ObjectType.IpAddress, true);
283                         if (bytes.length != 4)
284                                 throw new ResponseInvalidError ("Length '" + bytes.length
285                                                 + "' of IP address '" + bytes.toString ("hex")
286                                                 + "' is not 4");
287                         value = bytes[0] + "." + bytes[1] + "." + bytes[2] + "." + bytes[3];
288                 } else if (type == ObjectType.Counter) {
289                         value = readUint (buffer);
290                 } else if (type == ObjectType.Gauge) {
291                         value = readUint (buffer);
292                 } else if (type == ObjectType.TimeTicks) {
293                         value = readUint (buffer);
294                 } else if (type == ObjectType.Opaque) {
295                         value = buffer.readString (ObjectType.Opaque, true);
296                 } else if (type == ObjectType.Counter64) {
297                         value = readUint64 (buffer);
298                 } else if (type == ObjectType.NoSuchObject) {
299                         buffer.readByte ();
300                         buffer.readByte ();
301                         value = null;
302                 } else if (type == ObjectType.NoSuchInstance) {
303                         buffer.readByte ();
304                         buffer.readByte ();
305                         value = null;
306                 } else if (type == ObjectType.EndOfMibView) {
307                         buffer.readByte ();
308                         buffer.readByte ();
309                         value = null;
310                 } else {
311                         throw new ResponseInvalidError ("Unknown type '" + type
312                                         + "' in response");
313                 }
314
315                 varbinds.push ({
316                         oid: oid,
317                         type: type,
318                         value: value
319                 });
320         }
321 }
322
323 function writeUint (buffer, type, value) {
324         var b = new Buffer (4);
325         b.writeUInt32BE (value, 0);
326         buffer.writeBuffer (b, type);
327 }
328
329 function writeUint64 (buffer, value) {
330         if (value.length > 8)
331                 throw new RequestInvalidError ("64 bit unsigned integer too long '"
332                                 + value.length + "'")
333         buffer.writeBuffer (value, ObjectType.Counter64);
334 }
335
336 function writeVarbinds (buffer, varbinds) {
337         buffer.startSequence ();
338         for (var i = 0; i < varbinds.length; i++) {
339                 buffer.startSequence ();
340                 buffer.writeOID (varbinds[i].oid);
341
342                 if (varbinds[i].type && varbinds[i].hasOwnProperty("value")) {
343                         var type = varbinds[i].type;
344                         var value = varbinds[i].value;
345
346                         if (type == ObjectType.Boolean) {
347                                 buffer.writeBoolean (value ? true : false);
348                         } else if (type == ObjectType.Integer) { // also Integer32
349                                 buffer.writeInt (value);
350                         } else if (type == ObjectType.OctetString) {
351                                 if (typeof value == "string")
352                                         buffer.writeString (value);
353                                 else
354                                         buffer.writeBuffer (value, ObjectType.OctetString);
355                         } else if (type == ObjectType.Null) {
356                                 buffer.writeNull ();
357                         } else if (type == ObjectType.OID) {
358                                 buffer.writeOID (value);
359                         } else if (type == ObjectType.IpAddress) {
360                                 var bytes = value.split (".");
361                                 if (bytes.length != 4)
362                                         throw new RequestInvalidError ("Invalid IP address '"
363                                                         + value + "'");
364                                 buffer.writeBuffer (new Buffer (bytes), 64);
365                         } else if (type == ObjectType.Counter) { // also Counter32
366                                 writeUint (buffer, ObjectType.Counter, value);
367                         } else if (type == ObjectType.Gauge) { // also Gauge32 & Unsigned32
368                                 writeUint (buffer, ObjectType.Gauge, value);
369                         } else if (type == ObjectType.TimeTicks) {
370                                 writeUint (buffer, ObjectType.TimeTicks, value);
371                         } else if (type == ObjectType.Opaque) {
372                                 buffer.writeBuffer (value, ObjectType.Opaque);
373                         } else if (type == ObjectType.Counter64) {
374                                 writeUint64 (buffer, value);
375                         } else {
376                                 throw new RequestInvalidError ("Unknown type '" + type
377                                                 + "' in request");
378                         }
379                 } else {
380                         buffer.writeNull ();
381                 }
382
383                 buffer.endSequence ();
384         };
385         buffer.endSequence ();
386 }
387
388 /*****************************************************************************
389  ** PDU class definitions
390  **/
391
392 var SimplePdu = function (id, varbinds, options) {
393         this.id = id;
394         this.varbinds = varbinds;
395         this.options = options || {};
396 };
397
398 SimplePdu.prototype.toBuffer = function (buffer) {
399         buffer.startSequence (this.type);
400
401         buffer.writeInt (this.id);
402         buffer.writeInt ((this.type == PduType.GetBulkRequest)
403                         ? (this.options.nonRepeaters || 0)
404                         : 0);
405         buffer.writeInt ((this.type == PduType.GetBulkRequest)
406                         ? (this.options.maxRepetitions || 0)
407                         : 0);
408
409         writeVarbinds (buffer, this.varbinds);
410
411         buffer.endSequence ();
412 };
413
414 var GetBulkRequestPdu = function () {
415         this.type = PduType.GetBulkRequest;
416         GetBulkRequestPdu.super_.apply (this, arguments);
417 };
418
419 util.inherits (GetBulkRequestPdu, SimplePdu);
420
421 var GetNextRequestPdu = function () {
422         this.type = PduType.GetNextRequest;
423         GetNextRequestPdu.super_.apply (this, arguments);
424 };
425
426 util.inherits (GetNextRequestPdu, SimplePdu);
427
428 var GetResponsePdu = function (buffer) {
429         this.type = PduType.GetResponse;
430
431         buffer.readSequence (this.type);
432
433         this.id = buffer.readInt ();
434
435         this.errorStatus = buffer.readInt ();
436         this.errorIndex = buffer.readInt ();
437
438         this.varbinds = [];
439
440         readVarbinds (buffer, this.varbinds);
441 };
442
443 var GetRequestPdu = function () {
444         this.type = PduType.GetRequest;
445         GetRequestPdu.super_.apply (this, arguments);
446 };
447
448 util.inherits (GetRequestPdu, SimplePdu);
449
450 var InformRequestPdu = function () {
451         this.type = PduType.InformRequest;
452         InformRequestPdu.super_.apply (this, arguments);
453 };
454
455 util.inherits (InformRequestPdu, SimplePdu);
456
457 var SetRequestPdu = function () {
458         this.type = PduType.SetRequest;
459         SetRequestPdu.super_.apply (this, arguments);
460 };
461
462 util.inherits (SetRequestPdu, SimplePdu);
463
464 var TrapPdu = function (typeOrOid, varbinds, options) {
465         this.type = PduType.Trap;
466
467         this.agentAddr = options.agentAddr || "127.0.0.1";
468         this.upTime = options.upTime;
469
470         if (typeof typeOrOid == "string") {
471                 this.generic = TrapType.EnterpriseSpecific;
472                 this.specific = parseInt (typeOrOid.match (/\.(\d+)$/)[1]);
473                 this.enterprise = typeOrOid.replace (/\.(\d+)$/, "");
474         } else {
475                 this.generic = typeOrOid;
476                 this.specific = 0;
477                 this.enterprise = "1.3.6.1.4.1";
478         }
479
480         this.varbinds = varbinds;
481 };
482
483 TrapPdu.prototype.toBuffer = function (buffer) {
484         buffer.startSequence (this.type);
485
486         buffer.writeOID (this.enterprise);
487         buffer.writeBuffer (new Buffer (this.agentAddr.split (".")),
488                         ObjectType.IpAddress);
489         buffer.writeInt (this.generic);
490         buffer.writeInt (this.specific);
491         writeUint (buffer, ObjectType.TimeTicks,
492                         this.upTime || Math.floor (process.uptime () * 100));
493
494         writeVarbinds (buffer, this.varbinds);
495
496         buffer.endSequence ();
497 };
498
499 var TrapV2Pdu = function () {
500         this.type = PduType.TrapV2;
501         TrapV2Pdu.super_.apply (this, arguments);
502 };
503
504 util.inherits (TrapV2Pdu, SimplePdu);
505
506 /*****************************************************************************
507  ** Message class definitions
508  **/
509
510 var RequestMessage = function (version, community, pdu) {
511         this.version = version;
512         this.community = community;
513         this.pdu = pdu;
514 };
515
516 RequestMessage.prototype.toBuffer = function () {
517         if (this.buffer)
518                 return this.buffer;
519
520         var writer = new ber.Writer ();
521
522         writer.startSequence ();
523
524         writer.writeInt (this.version);
525         writer.writeString (this.community);
526
527         this.pdu.toBuffer (writer);
528
529         writer.endSequence ();
530
531         this.buffer = writer.buffer;
532
533         return this.buffer;
534 };
535
536 var ResponseMessage = function (buffer) {
537         var reader = new ber.Reader (buffer);
538
539         reader.readSequence ();
540
541         this.version = reader.readInt ();
542         this.community = reader.readString ();
543
544         var type = reader.peek ();
545
546         if (type == PduType.GetResponse) {
547                 this.pdu = new GetResponsePdu (reader);
548         } else {
549                 throw new ResponseInvalidError ("Unknown PDU type '" + type
550                                 + "' in response");
551         }
552 }
553
554 /*****************************************************************************
555  ** Session class definition
556  **/
557
558 var Session = function (target, community, options) {
559         this.target = target || "127.0.0.1";
560         this.community = community || "public";
561
562         this.version = (options && options.version)
563                         ? options.version
564                         : Version1;
565
566         this.transport = (options && options.transport)
567                         ? options.transport
568                         : "udp4";
569         this.port = (options && options.port )
570                         ? options.port
571                         : 161;
572         this.trapPort = (options && options.trapPort )
573                         ? options.trapPort
574                         : 162;
575
576         this.retries = (options && (options.retries || options.retries == 0))
577                         ? options.retries
578                         : 1;
579         this.timeout = (options && options.timeout)
580                         ? options.timeout
581                         : 5000;
582
583         this.sourceAddress = (options && options.sourceAddress )
584                         ? options.sourceAddress
585                         : undefined;
586         this.sourcePort = (options && options.sourcePort )
587                         ? parseInt(options.sourcePort)
588                         : undefined;
589
590         this.reqs = {};
591         this.reqCount = 0;
592
593         this.dgram = dgram.createSocket (this.transport);
594         this.dgram.unref();
595         
596         var me = this;
597         this.dgram.on ("message", me.onMsg.bind (me));
598         this.dgram.on ("close", me.onClose.bind (me));
599         this.dgram.on ("error", me.onError.bind (me));
600
601         if (this.sourceAddress || this.sourcePort)
602                 req.dgram.bind (this.sourcePort, this.sourceAddress);
603 };
604
605 util.inherits (Session, events.EventEmitter);
606
607 Session.prototype.close = function () {
608         this.dgram.close ();
609         return this;
610 }
611
612 Session.prototype.cancelRequests = function (error) {
613         for (id in this.reqs) {
614                 var req = this.reqs[id];
615                 this.unregisterRequest (req.id);
616                 req.responseCb (error);
617         }
618 }
619
620 function _generateId () {
621         return Math.floor (Math.random () + Math.random () * 10000000)
622 }
623
624 Session.prototype.get = function (oids, responseCb) {
625         function feedCb (req, message) {
626                 var pdu = message.pdu;
627                 var varbinds = [];
628
629                 if (req.message.pdu.varbinds.length != pdu.varbinds.length) {
630                         req.responseCb (new ResponseInvalidError ("Requested OIDs do not "
631                                         + "match response OIDs"));
632                 } else {
633                         for (var i = 0; i < req.message.pdu.varbinds.length; i++) {
634                                 if (req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid) {
635                                         req.responseCb (new ResponseInvalidError ("OID '"
636                                                         + req.message.pdu.varbinds[i].oid
637                                                         + "' in request at positiion '" + i + "' does not "
638                                                         + "match OID '" + pdu.varbinds[i].oid + "' in response "
639                                                         + "at position '" + i + "'"));
640                                         return;
641                                 } else {
642                                         varbinds.push (pdu.varbinds[i]);
643                                 }
644                         }
645
646                         req.responseCb (null, varbinds);
647                 }
648         };
649
650         var pduVarbinds = [];
651
652         for (var i = 0; i < oids.length; i++) {
653                 var varbind = {
654                         oid: oids[i]
655                 };
656                 pduVarbinds.push (varbind);
657         }
658
659         this.simpleGet (GetRequestPdu, feedCb, pduVarbinds, responseCb);
660
661         return this;
662 };
663
664 Session.prototype.getBulk = function () {
665         var oids, nonRepeaters, maxRepetitions, responseCb;
666
667         if (arguments.length >= 4) {
668                 oids = arguments[0];
669                 nonRepeaters = arguments[1];
670                 maxRepetitions = arguments[2];
671                 responseCb = arguments[3];
672         } else if (arguments.length >= 3) {
673                 oids = arguments[0];
674                 nonRepeaters = arguments[1];
675                 maxRepetitions = 10;
676                 responseCb = arguments[2];
677         } else {
678                 oids = arguments[0];
679                 nonRepeaters = 0;
680                 maxRepetitions = 10;
681                 responseCb = arguments[1];
682         }
683
684         function feedCb (req, message) {
685                 var pdu = message.pdu;
686                 var varbinds = [];
687                 var i = 0;
688
689                 // first walk through and grab non-repeaters
690                 if (pdu.varbinds.length < nonRepeaters) {
691                         req.responseCb (new ResponseInvalidError ("Varbind count in "
692                                         + "response '" + pdu.varbinds.length + "' is less than "
693                                         + "non-repeaters '" + nonRepeaters + "' in request"));
694                 } else {
695                         for ( ; i < nonRepeaters; i++) {
696                                 if (isVarbindError (pdu.varbinds[i])) {
697                                         varbinds.push (pdu.varbinds[i]);
698                                 } else if (! oidFollowsOid (req.message.pdu.varbinds[i].oid,
699                                                 pdu.varbinds[i].oid)) {
700                                         req.responseCb (new ResponseInvalidError ("OID '"
701                                                         + req.message.pdu.varbinds[i].oid + "' in request at "
702                                                         + "positiion '" + i + "' does not precede "
703                                                         + "OID '" + pdu.varbinds[i].oid + "' in response "
704                                                         + "at position '" + i + "'"));
705                                         return;
706                                 } else {
707                                         varbinds.push (pdu.varbinds[i]);
708                                 }
709                         }
710                 }
711
712                 var repeaters = req.message.pdu.varbinds.length - nonRepeaters;
713
714                 // secondly walk through and grab repeaters
715                 if (pdu.varbinds.length % (repeaters)) {
716                         req.responseCb (new ResponseInvalidError ("Varbind count in "
717                                         + "response '" + pdu.varbinds.length + "' is not a "
718                                         + "multiple of repeaters '" + repeaters
719                                         + "' plus non-repeaters '" + nonRepeaters + "' in request"));
720                 } else {
721                         while (i < pdu.varbinds.length) {
722                                 for (var j = 0; j < repeaters; j++, i++) {
723                                         var reqIndex = nonRepeaters + j;
724                                         var respIndex = i;
725
726                                         if (isVarbindError (pdu.varbinds[respIndex])) {
727                                                 if (! varbinds[reqIndex])
728                                                         varbinds[reqIndex] = [];
729                                                 varbinds[reqIndex].push (pdu.varbinds[respIndex]);
730                                         } else if (! oidFollowsOid (
731                                                         req.message.pdu.varbinds[reqIndex].oid,
732                                                         pdu.varbinds[respIndex].oid)) {
733                                                 req.responseCb (new ResponseInvalidError ("OID '"
734                                                                 + req.message.pdu.varbinds[reqIndex].oid
735                                                                 + "' in request at positiion '" + (reqIndex)
736                                                                 + "' does not precede OID '"
737                                                                 + pdu.varbinds[respIndex].oid
738                                                                 + "' in response at position '" + (respIndex) + "'"));
739                                                 return;
740                                         } else {
741                                                 if (! varbinds[reqIndex])
742                                                         varbinds[reqIndex] = [];
743                                                 varbinds[reqIndex].push (pdu.varbinds[respIndex]);
744                                         }
745                                 }
746                         }
747                 }
748
749                 req.responseCb (null, varbinds);
750         };
751
752         var pduVarbinds = [];
753
754         for (var i = 0; i < oids.length; i++) {
755                 var varbind = {
756                         oid: oids[i]
757                 };
758                 pduVarbinds.push (varbind);
759         }
760
761         var options = {
762                 nonRepeaters: nonRepeaters,
763                 maxRepetitions: maxRepetitions
764         };
765
766         this.simpleGet (GetBulkRequestPdu, feedCb, pduVarbinds, responseCb,
767                         options);
768
769         return this;
770 };
771
772 Session.prototype.getNext = function (oids, responseCb) {
773         function feedCb (req, message) {
774                 var pdu = message.pdu;
775                 var varbinds = [];
776
777                 if (req.message.pdu.varbinds.length != pdu.varbinds.length) {
778                         req.responseCb (new ResponseInvalidError ("Requested OIDs do not "
779                                         + "match response OIDs"));
780                 } else {
781                         for (var i = 0; i < req.message.pdu.varbinds.length; i++) {
782                                 if (isVarbindError (pdu.varbinds[i])) {
783                                         varbinds.push (pdu.varbinds[i]);
784                                 } else if (! oidFollowsOid (req.message.pdu.varbinds[i].oid,
785                                                 pdu.varbinds[i].oid)) {
786                                         req.responseCb (new ResponseInvalidError ("OID '"
787                                                         + req.message.pdu.varbinds[i].oid + "' in request at "
788                                                         + "positiion '" + i + "' does not precede "
789                                                         + "OID '" + pdu.varbinds[i].oid + "' in response "
790                                                         + "at position '" + i + "'"));
791                                         return;
792                                 } else {
793                                         varbinds.push (pdu.varbinds[i]);
794                                 }
795                         }
796
797                         req.responseCb (null, varbinds);
798                 }
799         };
800
801         var pduVarbinds = [];
802
803         for (var i = 0; i < oids.length; i++) {
804                 var varbind = {
805                         oid: oids[i]
806                 };
807                 pduVarbinds.push (varbind);
808         }
809
810         this.simpleGet (GetNextRequestPdu, feedCb, pduVarbinds, responseCb);
811
812         return this;
813 };
814
815 Session.prototype.inform = function () {
816         var typeOrOid = arguments[0];;
817         var varbinds, options = {}, responseCb;
818
819         /**
820          ** Support the following signatures:
821          ** 
822          **    typeOrOid, varbinds, options, callback
823          **    typeOrOid, varbinds, callback
824          **    typeOrOid, options, callback
825          **    typeOrOid, callback
826          **/
827         if (arguments.length >= 4) {
828                 varbinds = arguments[1];
829                 options = arguments[2];
830                 responseCb = arguments[3];
831         } else if (arguments.length >= 3) {
832                 if (arguments[1].constructor != Array) {
833                         varbinds = [];
834                         options = arguments[1];
835                         responseCb = arguments[2];
836                 } else {
837                         varbinds = arguments[1];
838                         responseCb = arguments[2];
839                 }
840         } else {
841                 varbinds = [];
842                 responseCb = arguments[1];
843         }
844
845         function feedCb (req, message) {
846                 var pdu = message.pdu;
847                 var varbinds = [];
848
849                 if (req.message.pdu.varbinds.length != pdu.varbinds.length) {
850                         req.responseCb (new ResponseInvalidError ("Inform OIDs do not "
851                                         + "match response OIDs"));
852                 } else {
853                         for (var i = 0; i < req.message.pdu.varbinds.length; i++) {
854                                 if (req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid) {
855                                         req.responseCb (new ResponseInvalidError ("OID '"
856                                                         + req.message.pdu.varbinds[i].oid
857                                                         + "' in inform at positiion '" + i + "' does not "
858                                                         + "match OID '" + pdu.varbinds[i].oid + "' in response "
859                                                         + "at position '" + i + "'"));
860                                         return;
861                                 } else {
862                                         varbinds.push (pdu.varbinds[i]);
863                                 }
864                         }
865
866                         req.responseCb (null, varbinds);
867                 }
868         };
869
870         if (typeof typeOrOid != "string")
871                 typeOrOid = "1.3.6.1.6.3.1.1.5." + (typeOrOid + 1);
872
873         var pduVarbinds = [
874                 {
875                         oid: "1.3.6.1.2.1.1.3.0",
876                         type: ObjectType.TimeTicks,
877                         value: options.upTime || Math.floor (process.uptime () * 100)
878                 },
879                 {
880                         oid: "1.3.6.1.6.3.1.1.4.1.0",
881                         type: ObjectType.OID,
882                         value: typeOrOid
883                 }
884         ];
885
886         for (var i = 0; i < varbinds.length; i++) {
887                 var varbind = {
888                         oid: varbinds[i].oid,
889                         type: varbinds[i].type,
890                         value: varbinds[i].value
891                 };
892                 pduVarbinds.push (varbind);
893         }
894         
895         options.port = this.trapPort;
896
897         this.simpleGet (InformRequestPdu, feedCb, pduVarbinds, responseCb, options);
898
899         return this;
900 };
901
902 Session.prototype.onClose = function () {
903         this.cancelRequests (new Error ("Socket forcibly closed"));
904         this.emit ("close");
905 };
906
907 Session.prototype.onError = function (error) {
908         this.emit (error);
909 };
910
911 Session.prototype.onMsg = function (buffer, remote) {
912         try {
913                 var message = new ResponseMessage (buffer);
914
915                 var req = this.unregisterRequest (message.pdu.id);
916                 if (! req)
917                         return;
918
919                 try {
920                         if (message.version != req.message.version) {
921                                 req.responseCb (new ResponseInvalidError ("Version in request '"
922                                                 + req.message.version + "' does not match version in "
923                                                 + "response '" + message.version));
924                         } else if (message.community != req.message.community) {
925                                 req.responseCb (new ResponseInvalidError ("Community '"
926                                                 + req.message.community + "' in request does not match "
927                                                 + "community '" + message.community + "' in response"));
928                         } else if (message.pdu.type == PduType.GetResponse) {
929                                 req.onResponse (req, message);
930                         } else {
931                                 req.responseCb (new ResponseInvalidError ("Unknown PDU type '"
932                                                 + message.pdu.type + "' in response"));
933                         }
934                 } catch (error) {
935                         req.responseCb (error);
936                 }
937         } catch (error) {
938                 this.emit("error", error);
939         }
940 };
941
942 Session.prototype.onSimpleGetResponse = function (req, message) {
943         var pdu = message.pdu;
944
945         if (pdu.errorStatus > 0) {
946                 var statusString = ErrorStatus[pdu.errorStatus]
947                                 || ErrorStatus.GeneralError;
948                 var statusCode = ErrorStatus[statusString]
949                                 || ErrorStatus[ErrorStatus.GeneralError];
950
951                 if (pdu.errorIndex <= 0 || pdu.errorIndex > pdu.varbinds.length) {
952                         req.responseCb (new RequestFailedError (statusString, statusCode));
953                 } else {
954                         var oid = pdu.varbinds[pdu.errorIndex - 1].oid;
955                         var error = new RequestFailedError (statusString + ": " + oid,
956                                         statusCode);
957                         req.responseCb (error);
958                 }
959         } else {
960                 req.feedCb (req, message);
961         }
962 };
963
964 Session.prototype.registerRequest = function (req) {
965         if (! this.reqs[req.id]) {
966                 this.reqs[req.id] = req;
967                 if (this.reqCount <= 0)
968                         this.dgram.ref();
969                 this.reqCount++;
970         }
971         var me = this;
972         req.timer = setTimeout (function () {
973                 if (req.retries-- > 0) {
974                         me.send (req);
975                 } else {
976                         me.unregisterRequest (req.id);
977                         req.responseCb (new RequestTimedOutError (
978                                         "Request timed out"));
979                 }
980         }, req.timeout);
981 };
982
983 Session.prototype.send = function (req, noWait) {
984         try {
985                 var me = this;
986                 
987                 var buffer = req.message.toBuffer ();
988
989                 this.dgram.send (buffer, 0, buffer.length, req.port, this.target,
990                                 function (error, bytes) {
991                         if (error) {
992                                 req.responseCb (error);
993                         } else {
994                                 if (noWait) {
995                                         req.responseCb (null);
996                                 } else {
997                                         me.registerRequest (req);
998                                 }
999                         }
1000                 });
1001         } catch (error) {
1002                 req.responseCb (error);
1003         }
1004         
1005         return this;
1006 };
1007
1008 Session.prototype.set = function (varbinds, responseCb) {
1009         function feedCb (req, message) {
1010                 var pdu = message.pdu;
1011                 var varbinds = [];
1012
1013                 if (req.message.pdu.varbinds.length != pdu.varbinds.length) {
1014                         req.responseCb (new ResponseInvalidError ("Requested OIDs do not "
1015                                         + "match response OIDs"));
1016                 } else {
1017                         for (var i = 0; i < req.message.pdu.varbinds.length; i++) {
1018                                 if (req.message.pdu.varbinds[i].oid != pdu.varbinds[i].oid) {
1019                                         req.responseCb (new ResponseInvalidError ("OID '"
1020                                                         + req.message.pdu.varbinds[i].oid
1021                                                         + "' in request at positiion '" + i + "' does not "
1022                                                         + "match OID '" + pdu.varbinds[i].oid + "' in response "
1023                                                         + "at position '" + i + "'"));
1024                                         return;
1025                                 } else {
1026                                         varbinds.push (pdu.varbinds[i]);
1027                                 }
1028                         }
1029
1030                         req.responseCb (null, varbinds);
1031                 }
1032         };
1033
1034         var pduVarbinds = [];
1035
1036         for (var i = 0; i < varbinds.length; i++) {
1037                 var varbind = {
1038                         oid: varbinds[i].oid,
1039                         type: varbinds[i].type,
1040                         value: varbinds[i].value
1041                 };
1042                 pduVarbinds.push (varbind);
1043         }
1044
1045         this.simpleGet (SetRequestPdu, feedCb, pduVarbinds, responseCb);
1046
1047         return this;
1048 };
1049
1050 Session.prototype.simpleGet = function (pduClass, feedCb, varbinds,
1051                 responseCb, options) {
1052         var req = {}
1053
1054         try {
1055                 var id = _generateId ();
1056                 var pdu = new pduClass (id, varbinds, options);
1057                 var message = new RequestMessage (this.version, this.community, pdu);
1058
1059                 req = {
1060                         id: id,
1061                         message: message,
1062                         responseCb: responseCb,
1063                         retries: this.retries,
1064                         timeout: this.timeout,
1065                         onResponse: this.onSimpleGetResponse,
1066                         feedCb: feedCb,
1067                         port: (options && options.port) ? options.port : this.port
1068                 };
1069
1070                 this.send (req);
1071         } catch (error) {
1072                 if (req.responseCb)
1073                         req.responseCb (error);
1074         }
1075 };
1076
1077 function subtreeCb (req, varbinds) {
1078         var done = 0;
1079
1080         for (var i = varbinds.length; i > 0; i--) {
1081                 if (! oidInSubtree (req.baseOid, varbinds[i - 1].oid)) {
1082                         done = 1;
1083                         varbinds.pop ();
1084                 }
1085         }
1086
1087         if (varbinds.length > 0)
1088                 req.feedCb (varbinds);
1089
1090         if (done)
1091                 return true;
1092 }
1093
1094 Session.prototype.subtree  = function () {
1095         var me = this;
1096         var oid = arguments[0];
1097         var maxRepetitions, feedCb, doneCb;
1098
1099         if (arguments.length < 4) {
1100                 maxRepetitions = 20;
1101                 feedCb = arguments[1];
1102                 doneCb = arguments[2];
1103         } else {
1104                 maxRepetitions = arguments[1];
1105                 feedCb = arguments[2];
1106                 doneCb = arguments[3];
1107         }
1108
1109         var req = {
1110                 feedCb: feedCb,
1111                 doneCb: doneCb,
1112                 maxRepetitions: maxRepetitions,
1113                 baseOid: oid
1114         };
1115
1116         this.walk (oid, maxRepetitions, subtreeCb.bind (me, req), doneCb);
1117
1118         return this;
1119 }
1120
1121 function tableColumnsResponseCb (req, error) {
1122         if (error) {
1123                 req.responseCb (error);
1124         } else if (req.error) {
1125                 req.responseCb (req.error);
1126         } else {
1127                 if (req.columns.length > 0) {
1128                         var column = req.columns.pop ();
1129                         var me = this;
1130                         this.subtree (req.rowOid + column, req.maxRepetitions,
1131                                         tableColumnsFeedCb.bind (me, req),
1132                                         tableColumnsResponseCb.bind (me, req));
1133                 } else {
1134                         req.responseCb (null, req.table);
1135                 }
1136         }
1137 }
1138
1139 function tableColumnsFeedCb (req, varbinds) {
1140         for (var i = 0; i < varbinds.length; i++) {
1141                 if (isVarbindError (varbinds[i])) {
1142                         req.error = new RequestFailedError (varbindError (varbind[i]));
1143                         return true;
1144                 }
1145
1146                 var oid = varbinds[i].oid.replace (req.rowOid, "")
1147                 if (oid && oid != varbinds[i].oid) {
1148                         var match = oid.match (/^(\d+)\.(.+)$/);
1149                         if (match && match[1] > 0) {
1150                                 if (! req.table[match[2]])
1151                                         req.table[match[2]] = {};
1152                                 req.table[match[2]][match[1]] = varbinds[i].value;
1153                         }
1154                 }
1155         }
1156 }
1157
1158 Session.prototype.tableColumns = function () {
1159         var me = this;
1160
1161         var oid = arguments[0];
1162         var columns = arguments[1];
1163         var maxRepetitions, responseCb;
1164
1165         if (arguments.length < 4) {
1166                 responseCb = arguments[2];
1167                 maxRepetitions = 20;
1168         } else {
1169                 maxRepetitions = arguments[2];
1170                 responseCb = arguments[3];
1171         }
1172
1173         var req = {
1174                 responseCb: responseCb,
1175                 maxRepetitions: maxRepetitions,
1176                 baseOid: oid,
1177                 rowOid: oid + ".1.",
1178                 columns: columns.slice(0),
1179                 table: {}
1180         };
1181
1182         if (req.columns.length > 0) {
1183                 var column = req.columns.pop ();
1184                 this.subtree (req.rowOid + column, maxRepetitions,
1185                                 tableColumnsFeedCb.bind (me, req),
1186                                 tableColumnsResponseCb.bind (me, req));
1187         }
1188
1189         return this;
1190 }
1191
1192 function tableResponseCb (req, error) {
1193         if (error)
1194                 req.responseCb (error);
1195         else if (req.error)
1196                 req.responseCb (req.error);
1197         else
1198                 req.responseCb (null, req.table);
1199 }
1200
1201 function tableFeedCb (req, varbinds) {
1202         for (var i = 0; i < varbinds.length; i++) {
1203                 if (isVarbindError (varbinds[i])) {
1204                         req.error = new RequestFailedError (varbindError (varbind[i]));
1205                         return true;
1206                 }
1207
1208                 var oid = varbinds[i].oid.replace (req.rowOid, "")
1209                 if (oid && oid != varbinds[i].oid) {
1210                         var match = oid.match (/^(\d+)\.(.+)$/);
1211                         if (match && match[1] > 0) {
1212                                 if (! req.table[match[2]])
1213                                         req.table[match[2]] = {};
1214                                 req.table[match[2]][match[1]] = varbinds[i].value;
1215                         }
1216                 }
1217         }
1218 }
1219
1220 Session.prototype.table = function () {
1221         var me = this;
1222
1223         var oid = arguments[0];
1224         var maxRepetitions, responseCb;
1225
1226         if (arguments.length < 3) {
1227                 responseCb = arguments[1];
1228                 maxRepetitions = 20;
1229         } else {
1230                 maxRepetitions = arguments[1];
1231                 responseCb = arguments[2];
1232         }
1233
1234         var req = {
1235                 responseCb: responseCb,
1236                 maxRepetitions: maxRepetitions,
1237                 baseOid: oid,
1238                 rowOid: oid + ".1.",
1239                 table: {}
1240         };
1241
1242         this.subtree (oid, maxRepetitions, tableFeedCb.bind (me, req),
1243                         tableResponseCb.bind (me, req));
1244
1245         return this;
1246 }
1247
1248 Session.prototype.trap = function () {
1249         var req = {};
1250
1251         try {
1252                 var typeOrOid = arguments[0];
1253                 var varbinds, options = {}, responseCb;
1254
1255                 /**
1256                  ** Support the following signatures:
1257                  ** 
1258                  **    typeOrOid, varbinds, options, callback
1259                  **    typeOrOid, varbinds, agentAddr, callback
1260                  **    typeOrOid, varbinds, callback
1261                  **    typeOrOid, agentAddr, callback
1262                  **    typeOrOid, options, callback
1263                  **    typeOrOid, callback
1264                  **/
1265                 if (arguments.length >= 4) {
1266                         varbinds = arguments[1];
1267                         if (typeof arguments[2] == "string") {
1268                                 options.agentAddr = arguments[2];
1269                         } else if (arguments[2].constructor != Array) {
1270                                 options = arguments[2];
1271                         }
1272                         responseCb = arguments[3];
1273                 } else if (arguments.length >= 3) {
1274                         if (typeof arguments[1] == "string") {
1275                                 varbinds = [];
1276                                 options.agentAddr = arguments[1];
1277                         } else if (arguments[1].constructor != Array) {
1278                                 varbinds = [];
1279                                 options = arguments[1];
1280                         } else {
1281                                 varbinds = arguments[1];
1282                                 agentAddr = null;
1283                         }
1284                         responseCb = arguments[2];
1285                 } else {
1286                         varbinds = [];
1287                         responseCb = arguments[1];
1288                 }
1289
1290                 var pdu, pduVarbinds = [];
1291
1292                 for (var i = 0; i < varbinds.length; i++) {
1293                         var varbind = {
1294                                 oid: varbinds[i].oid,
1295                                 type: varbinds[i].type,
1296                                 value: varbinds[i].value
1297                         };
1298                         pduVarbinds.push (varbind);
1299                 }
1300                 
1301                 var id = _generateId ();
1302
1303                 if (this.version == Version2c) {
1304                         if (typeof typeOrOid != "string")
1305                                 typeOrOid = "1.3.6.1.6.3.1.1.5." + (typeOrOid + 1);
1306
1307                         pduVarbinds.unshift (
1308                                 {
1309                                         oid: "1.3.6.1.2.1.1.3.0",
1310                                         type: ObjectType.TimeTicks,
1311                                         value: options.upTime || Math.floor (process.uptime () * 100)
1312                                 },
1313                                 {
1314                                         oid: "1.3.6.1.6.3.1.1.4.1.0",
1315                                         type: ObjectType.OID,
1316                                         value: typeOrOid
1317                                 }
1318                         );
1319
1320                         pdu = new TrapV2Pdu (id, pduVarbinds, options);
1321                 } else {
1322                         pdu = new TrapPdu (typeOrOid, pduVarbinds, options);
1323                 }
1324
1325                 var message = new RequestMessage (this.version, this.community, pdu);
1326
1327                 req = {
1328                         id: id,
1329                         message: message,
1330                         responseCb: responseCb,
1331                         port: this.trapPort
1332                 };
1333
1334                 this.send (req, true);
1335         } catch (error) {
1336                 if (req.responseCb)
1337                         req.responseCb (error);
1338         }
1339
1340         return this;
1341 };
1342
1343 Session.prototype.unregisterRequest = function (id) {
1344         var req = this.reqs[id];
1345         if (req) {
1346                 delete this.reqs[id];
1347                 clearTimeout (req.timer);
1348                 delete req.timer;
1349                 this.reqCount--;
1350                 if (this.reqCount <= 0)
1351                         this.dgram.unref();
1352                 return req;
1353         } else {
1354                 return null;
1355         }
1356 };
1357
1358 function walkCb (req, error, varbinds) {
1359         var done = 0;
1360         var oid;
1361
1362         if (error) {
1363                 if (error instanceof RequestFailedError) {
1364                         if (error.status != ErrorStatus.NoSuchName) {
1365                                 req.doneCb (error);
1366                                 return;
1367                         } else {
1368                                 // signal the version 1 walk code below that it should stop
1369                                 done = 1;
1370                         }
1371                 } else {
1372                         req.doneCb (error);
1373                         return;
1374                 }
1375         }
1376
1377         if (this.version == Version2c) {
1378                 for (var i = varbinds[0].length; i > 0; i--) {
1379                         if (varbinds[0][i - 1].type == ObjectType.EndOfMibView) {
1380                                 varbinds[0].pop ();
1381                                 done = 1;
1382                         }
1383                 }
1384                 if (req.feedCb (varbinds[0]))
1385                         done = 1;
1386                 if (! done)
1387                         oid = varbinds[0][varbinds[0].length - 1].oid;
1388         } else {
1389                 if (! done) {
1390                         if (req.feedCb (varbinds)) {
1391                                 done = 1;
1392                         } else {
1393                                 oid = varbinds[0].oid;
1394                         }
1395                 }
1396         }
1397
1398         if (done)
1399                 req.doneCb (null);
1400         else
1401                 this.walk (oid, req.maxRepetitions, req.feedCb, req.doneCb,
1402                                 req.baseOid);
1403 }
1404
1405 Session.prototype.walk  = function () {
1406         var me = this;
1407         var oid = arguments[0];
1408         var maxRepetitions, feedCb, doneCb, baseOid;
1409
1410         if (arguments.length < 4) {
1411                 maxRepetitions = 20;
1412                 feedCb = arguments[1];
1413                 doneCb = arguments[2];
1414         } else {
1415                 maxRepetitions = arguments[1];
1416                 feedCb = arguments[2];
1417                 doneCb = arguments[3];
1418         }
1419
1420         var req = {
1421                 maxRepetitions: maxRepetitions,
1422                 feedCb: feedCb,
1423                 doneCb: doneCb
1424         };
1425
1426         if (this.version == Version2c)
1427                 this.getBulk ([oid], 0, maxRepetitions,
1428                                 walkCb.bind (me, req));
1429         else
1430                 this.getNext ([oid], walkCb.bind (me, req));
1431
1432         return this;
1433 }
1434
1435 /*****************************************************************************
1436  ** Exports
1437  **/
1438
1439 exports.Session = Session;
1440
1441 exports.createSession = function (target, community, version, options) {
1442         return new Session (target, community, version, options);
1443 };
1444
1445 exports.isVarbindError = isVarbindError;
1446 exports.varbindError = varbindError;
1447
1448 exports.Version1 = Version1;
1449 exports.Version2c = Version2c;
1450
1451 exports.ErrorStatus = ErrorStatus;
1452 exports.TrapType = TrapType;
1453 exports.ObjectType = ObjectType;
1454
1455 exports.ResponseInvalidError = ResponseInvalidError;
1456 exports.RequestInvalidError = RequestInvalidError;
1457 exports.RequestFailedError = RequestFailedError;
1458 exports.RequestTimedOutError = RequestTimedOutError;
1459
1460 /**
1461  ** We've added this for testing.
1462  **/
1463 exports.ObjectParser = {
1464         readInt: readInt,
1465         readUint: readUint
1466 };