Coverage

96%
473
457
16

lib/asn1ber.js

98%
156
153
3
LineHitsSource
1// This file implements a minimal subset of Abstract Syntax Notation One (**ASN.1**)
2// Basic Encoding Rules (**BER**), namely the parts that are necessary for sending
3// and receiving SNMPv2c messages.
4//
5// (c) 2012 Jakob Borg, Nym Networks
6
7"use strict";
8
9// We define constants for the commonly used ASN.1 types in SNMP.
10
111var T = {
12 Integer: 0x02,
13 OctetString: 0x04,
14 Null: 0x05,
15 ObjectIdentifier: 0x06,
16 Sequence: 0x30,
17 IpAddress: 0x40,
18 Counter: 0x41,
19 Gauge: 0x42,
20 TimeTicks: 0x43,
21 Opaque: 0x44,
22 NsapAddress: 0x45,
23 Counter64: 0x46,
24 NoSuchObject: 0x80,
25 NoSuchInstance: 0x81,
26 EndOfMibView: 0x82,
27 PDUBase: 0xA0
28};
29
301var P = {
31 GetRequestPDU: 0x00,
32 GetNextRequestPDU: 0x01,
33 GetResponsePDU: 0x02,
34 SetRequestPDU: 0x03
35};
36
37// The types are also available for consumers of the library.
38
391exports.types = T;
401exports.pduTypes = P;
411exports.unittest = {};
42
43// Private helper functions
44// -----
45
46// Encode a length as it should be encoded.
47
48function lengthArray(len) {
49345 var arr = [];
50
51345 if (len <= 127) {
52 // Return a single byte if the value is 127 or less.
53339 return [ len ];
54 } else {
55 // Otherwise encode it as a MSB base-256 integer.
566 while (len > 0) {
578 arr.push(len % 256);
588 len = parseInt(len / 256, 10);
59 }
60 // Add a length byte in front and set the high bit to indicate
61 // that this is a longer value than one byte.
626 arr.push(128 + arr.length);
636 arr.reverse();
646 return arr;
65 }
66}
67
681exports.unittest.lengthArray = lengthArray;
69
70// Return a wrapped copy of the passed `contents`, with the specified wrapper type.
71// This is used for Sequence and other constructed types.
72
73function wrapper(type, contents) {
74266 var buf, len, i;
75
76 // Get the encoded length of the contents
77266 len = lengthArray(contents.length);
78
79 // Set up a buffer with the type and length bytes plus a straight copy of the content.
80266 buf = new Buffer(1 + contents.length + len.length);
81266 buf[0] = type;
82266 for (i = 1; i < len.length + 1; i++) {
83271 buf[i] = len[i - 1];
84 }
85266 contents.copy(buf, len.length + 1, 0);
86266 return buf;
87}
88
89// Get the encoded representation of a number in an OID.
90// If the number is less than 128, pass it as it is.
91// Otherwise return an array of base-128 digits, most significant first,
92// with the high bit set on all octets except the last one.
93// This encoding should be used for all number in an OID except the first
94// two (.1.3) which are handled specially.
95
96function oidInt(val) {
973 var bytes = [];
98
993 bytes.push(val % 128);
1003 val = parseInt(val / 128, 10);
1013 while (val > 127) {
1021 bytes.push(128 + val % 128);
1031 val = parseInt(val / 128, 10);
104 }
1053 bytes.push(val + 128);
1063 return bytes.reverse();
107}
108
109// Encode an OID. The first two number are encoded specially
110// in the first octet, then the rest are encoded as one octet per number
111// unless the number exceeds 127. If so, it's encoded as several base-127
112// octets with the high bit set to indicate continuation.
113
114function oidArray(oid) {
11576 var bytes, i, val;
116
117 // Enforce some minimum requirements on the OID.
11876 if (oid.length < 2) {
1191 throw new Error("Minimum OID length is two.");
12075 } else if (oid[0] !== 1 || oid[1] !== 3) {
1211 throw new Error("SNMP OIDs always start with .1.3.");
122 }
123
124 // Calculate the first byte of the encoded OID according to the 'special' rule.
12574 bytes = [ 40 * oid[0] + oid[1] ];
126
127 // For the rest of the OID, encode each number individually and add the
128 // resulting bytes to the buffer.
12974 for (i = 2; i < oid.length; i++) {
130511 val = oid[i];
131511 if (val > 127) {
1323 bytes = bytes.concat(oidInt(val));
133 } else {
134508 bytes.push(val);
135 }
136 }
137
13874 return bytes;
139}
140
141// Divide an integer into base-256 bytes.
142// Most significant byte first.
143function intArray(val) {
144198 var array = [];
145
146198 if (val === 0) {
14794 array.push(0);
148 } else {
149104 while (val > 0) {
150246 array.push(val % 256);
151246 val = parseInt(val / 256, 10);
152 }
153 }
154
155 // Do not produce integers that look negative (high bit
156 // of first byte set).
157198 if (array[array.length - 1] >= 0x80) {
1582 array.push(0);
159 }
160
161198 return array.reverse();
162}
163
164// Functions to encode ASN.1 from native objects
165// -----
166
167// Encode a simple integer.
168// Integers are encoded as a simple base-256 byte sequence, most significant byte first,
169// prefixed with a length byte. In principle we support arbitrary integer sizes, in practice
170// Javascript doesn't even **have** integers so some precision might get lost.
171
1721exports.encodeInteger = function (val) {
173198 var i, arr, buf;
174
175 // Get the bytes that we're going to encode.
176198 arr = intArray(val);
177
178 // Now that we know the length, we allocate a buffer of the required size.
179 // We set the type and length bytes appropriately.
180198 buf = new Buffer(2 + arr.length);
181198 buf[0] = T.Integer;
182198 buf[1] = arr.length;
183
184 // Copy the bytes into the array.
185198 for (i = 0; i < arr.length; i++) {
186342 buf[i + 2] = arr[i];
187 }
188
189198 return buf;
190};
191
192// Create the representation of a Null, `05 00`.
193
1941exports.encodeNull = function () {
19567 var buf = new Buffer(2);
19667 buf[0] = T.Null;
19767 buf[1] = 0;
19867 return buf;
199};
200
201// Encode a Sequence, which is a wrapper of type `30`.
202
2031exports.encodeSequence = function (contents) {
204168 return wrapper(T.Sequence, contents);
205};
206
207// Encode an OctetString, which is a wrapper of type `04`.
208
2091exports.encodeOctetString = function (string) {
21052 var buf, contents;
211
21252 if (typeof string === 'string') {
21349 contents = new Buffer(string);
2143 } else if (Buffer.isBuffer(string)) {
2151 contents = string;
216 } else {
2172 throw new Error('Only Buffer and string types are acceptable as OctetString.');
218 }
219
22050 return wrapper(T.OctetString, contents);
221};
222
223// Encode an IpAddress, which is a wrapper of type `40`.
224
2251exports.encodeIpAddress = function (address) {
2261 var contents, octets, value = [];
227
2281 if (typeof address !== 'string' && !Buffer.isBuffer(address)) {
2290 throw new Error('Only Buffer and string types are acceptable as OctetString.');
230 }
231
232 // assume that the string is in dotted decimal format ipv4
233 // also, use toString in case a buffer was passed in.
234
2351 octets = address.toString().split('.');
2361 if (octets.length !== 4) {
2370 throw new Error('IP Addresses must be specified in dotted decimal format.');
238 }
2391 octets.forEach(function (octet) {
2404 var octetValue = parseInt(octet, 10);
2414 if (octet < 0 || octet > 255) {
2420 throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets));
243 }
2444 value.push(octetValue);
245 });
246
2471 contents = new Buffer(value);
248
2491 return wrapper(T.IpAddress, contents);
250};
251
252// Encode an ObjectId.
253
2541exports.encodeOid = function (oid) {
25576 var buf, bytes, i, len;
256
257 // Get the encoded format of the OID.
25876 bytes = oidArray(oid);
259
260 // Get the encoded format of the length
26174 len = lengthArray(bytes.length);
262
263 // Fill in the buffer with type, length and OID data.
26474 buf = new Buffer(1 + bytes.length + len.length);
26574 buf[0] = T.ObjectIdentifier;
26674 for (i = 1; i < len.length + 1; i++) {
26774 buf[i] = len[i - 1];
268 }
26974 for (i = len.length + 1; i < bytes.length + len.length + 1; i++) {
270589 buf[i] = bytes[i - len.length - 1];
271 }
272
27374 return buf;
274};
275
276// Encode an SNMP request with specified `contents`.
277// The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`.
278
2791exports.encodeRequest = function (type, contents) {
28047 return wrapper(T.PDUBase + type, contents);
281};
282
283// Functions to parse ASN.1 to native objects
284// -----
285
286// Parse and return type, data length and header length.
287function typeAndLength(buf) {
288268 var res, len, i;
289
290268 res = { type: buf[0], len: 0, header: 1 };
291268 if (buf[1] < 128) {
292 // If bit 8 is zero, this byte indicates the content length (up to 127 bytes).
293261 res.len = buf[1];
294261 res.header += 1;
295 } else {
296 // If bit 8 is 1, bits 0 to 7 indicate the number of following legth bytes.
297 // These bytes are a simple msb base-256 integer indicating the content length.
2987 for (i = 0; i < buf[1] - 128; i++) {
29910 res.len += buf[i + 1];
30010 res.len *= 256;
301 }
3027 res.header += buf[1] - 128 + 1;
303 }
304268 return res;
305}
306
3071exports.typeAndLength = typeAndLength;
308
309// Parse a buffer containing a representation of an integer.
310// Verifies the type, then multiplies in each byte as it comes.
311
3121exports.parseInteger = function (buf) {
313281 var i, val;
314
315281 if (buf[0] !== T.Integer && buf[0] !== T.Counter &&
316 buf[0] !== T.Counter64 && buf[0] !== T.Gauge &&
317 buf[0] !== T.TimeTicks) {
3181 throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer');
319 }
320
321280 val = 0;
322280 for (i = 0; i < buf[1]; i++) {
323547 val *= 256;
324547 val += buf[i + 2];
325 }
326
327280 return val;
328};
329
330// Parse a buffer containing a representation of an OctetString.
331// Verify the type, then just grab the string out of the buffer.
332
3331exports.parseOctetString = function (buf) {
33478 if (buf[0] !== T.OctetString) {
3351 throw new Error('Buffer does not appear to be an OctetString');
336 }
337
338 // SNMP doesn't specify an encoding so I pick UTF-8 as the 'most standard'
339 // encoding. We'll see if that assumption survives contact with actual reality.
34077 return buf.toString('utf-8', 2, 2 + buf[1]);
341};
342
343// Parse a buffer containing a representation of an ObjectIdentifier.
344// Verify the type, then apply the relevent encoding rules.
345
3461exports.parseOid = function (buf) {
34790 var oid, val, i;
348
34990 if (buf[0] !== T.ObjectIdentifier) {
3501 throw new Error('Buffer does not appear to be an ObjectIdentifier');
351 }
352
353 // The first byte contains the first two numbers in the OID
35489 oid = [ parseInt(buf[2] / 40, 10), buf[2] % 40 ];
355
356 // The rest of the data is a base-128-encoded OID
35789 for (i = 0; i < buf[1] - 1; i++) {
358681 val = 0;
359681 while (buf[i + 3] >= 128) {
36017 val += buf[i + 3] - 128;
36117 val *= 128;
36217 i++;
363 }
364681 val += buf[i + 3];
365681 oid.push(val);
366 }
367
36889 return oid;
369};
370
371// Parse a buffer containing a representation of an array type.
372// This is for example an IpAddress.
373
3741exports.parseArray = function (buf) {
3753 var i, nelem, array;
376
3773 if (buf[0] !== T.IpAddress) {
3781 throw new Error('Buffer does not appear to be an array type.');
379 }
380
3812 nelem = buf[1];
3822 array = [];
383
3842 for (i = 0; i < buf[1]; i++) {
3858 array.push(buf[i + 2]);
386 }
387
3882 return array;
389};
390
391// Parse a buffer containing a representation of an opaque type.
392// This is for example an IpAddress.
393
3941exports.parseOpaque = function (buf) {
3954 var hdr;
396
3974 hdr = typeAndLength(buf);
398
3994 if (hdr.type !== T.Opaque) {
4001 throw new Error('Buffer does not appear to be an opaque type.');
401 }
402
4033 return '0x' + buf.slice(hdr.header).toString('hex');
404};
405
406/*globals exports: false*/
407

lib/snmp.js

95%
317
304
13
LineHitsSource
1// Introduction
2// -----
3// This is `node-snmp-native`, a native (Javascript) implementation of an SNMP
4// client library targeted at Node.js. It's MIT licensed and available at
5// https://github.com/calmh/node-snmp-native
6//
7// (c) 2012 Jakob Borg, Nym Networks
8
9"use strict";
10
11// Code
12// -----
13// This file implements a structure representing an SNMP message
14// and routines for converting to and from the network representation.
15
16// Define our external dependencies.
171var assert = require('assert');
181var dgram = require('dgram');
191var events = require('events');
20
21// We also need our ASN.1 BER en-/decoding routines.
221var asn1ber = require('./asn1ber');
23
24// Basic structures
25// ----
26
27// A `VarBind` is the innermost structure, containing an OID-Value pair.
28function VarBind() {
29223 this.type = 5;
30223 this.value = null;
31}
32
33// The `PDU` contains the SNMP request or response fields and a list of `VarBinds`.
34function PDU() {
35108 this.type = asn1ber.pduTypes.GetRequestPDU;
36108 this.reqid = 1;
37108 this.error = 0;
38108 this.errorIndex = 0;
39108 this.varbinds = [ new VarBind() ];
40}
41
42// The `Packet` contains the SNMP version and community and the `PDU`.
43function Packet() {
44108 this.version = 1;
45108 this.community = 'public';
46108 this.pdu = new PDU();
47}
48
49// Allow consumers to create packet structures from scratch.
501exports.Packet = Packet;
51
52// Private helper functions
53// ----
54
55// Concatenate several buffers to one.
56function concatBuffers(buffers) {
57210 var total, cur = 0, buf;
58
59 // First we calculate the total length,
60210 total = buffers.reduce(function (tot, b) {
61538 return tot + b.length;
62 }, 0);
63
64 // then we allocate a new Buffer large enough to contain all data,
65210 buf = new Buffer(total);
66210 buffers.forEach(function (buffer) {
67 // finally we copy the data into the new larger buffer.
68538 buffer.copy(buf, cur, 0);
69538 cur += buffer.length;
70 });
71
72210 return buf;
73}
74
75// Clear a pending packet when it times out or is successfully received.
76
77function clearRequest(reqs, reqid) {
7843 var self = this;
79
8043 var entry = reqs[reqid];
8143 if (entry) {
8243 if (entry.timeout) {
8342 clearTimeout(entry.timeout);
84 }
8543 delete reqs[reqid];
86 }
87}
88
89// Convert a string formatted OID to an array, leaving anything non-string alone.
90
91function parseSingleOid(oid) {
9279 if (typeof oid !== 'string') {
9359 return oid;
94 }
95
9620 if (oid[0] !== '.') {
974 throw new Error('Invalid OID format');
98 }
99
10016 oid = oid.split('.')
101 .filter(function (s) {
102154 return s.length > 0;
103 })
104 .map(function (s) {
105138 return parseInt(s, 10);
106 });
107
10816 return oid;
109}
110
111// Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings.
112
113function parseOids(options) {
11458 if (options.oid) {
11548 options.oid = parseSingleOid(options.oid);
116 }
11755 if (options.oids) {
1185 options.oids = options.oids.map(parseSingleOid);
119 }
120}
121
122// Update targ with attributes from _defs.
123// Any existing attributes on targ are untouched.
124
125function defaults(targ, _defs) {
126159 [].slice.call(arguments, 1).forEach(function (def) {
127164 Object.keys(def).forEach(function (key) {
128818 if (!targ.hasOwnProperty(key)) {
129468 targ[key] = def[key];
130 }
131 });
132 });
133}
134
135// Encode structure to ASN.1 BER
136// ----
137
138// Return an ASN.1 BER encoding of a Packet structure.
139// This is suitable for transmission on a UDP socket.
140function encode(pkt) {
14147 var version, community, reqid, err, erridx, vbs, pdu, message;
142
143 // We only support SNMPv2c, so enforce that version stamp.
14447 if (pkt.version !== 1) {
1450 throw new Error('Only SNMPv2c is supported.');
146 }
147
148 // Encode the message header fields.
14947 version = asn1ber.encodeInteger(pkt.version);
15047 community = asn1ber.encodeOctetString(pkt.community);
151
152 // Encode the PDU header fields.
15347 reqid = asn1ber.encodeInteger(pkt.pdu.reqid);
15447 err = asn1ber.encodeInteger(pkt.pdu.error);
15547 erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex);
156
157 // Encode the PDU varbinds.
15847 vbs = [];
15947 pkt.pdu.varbinds.forEach(function (vb) {
16073 var oid = asn1ber.encodeOid(vb.oid), val;
161
16273 if (vb.type === asn1ber.types.Null) {
16366 val = asn1ber.encodeNull();
1647 } else if (vb.type === asn1ber.types.Integer) {
1655 val = asn1ber.encodeInteger(vb.value);
1662 } else if (vb.type === asn1ber.types.IpAddress) {
1671 val = asn1ber.encodeIpAddress(vb.value);
1681 } else if (vb.type === asn1ber.types.OctetString) {
1691 val = asn1ber.encodeOctetString(vb.value);
170 } else {
1710 throw new Error('Unknown varbind type "' + vb.type + '" in encoding.');
172 }
17372 vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val])));
174 });
175
176 // Concatenate all the varbinds together.
17746 vbs = asn1ber.encodeSequence(concatBuffers(vbs));
178
179 // Create the PDU by concatenating the inner fields and adding a request structure around it.
18046 pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs]));
181
182 // Create the message by concatenating the header fields and the PDU.
18346 message = asn1ber.encodeSequence(concatBuffers([version, community, pdu]));
184
18546 return message;
186}
187
1881exports.encode = encode;
189
190// Parse ASN.1 BER into a structure
191// -----
192
193// Parse an SNMP packet into its component fields.
194// We don't do a lot of validation so a malformed packet will probably just
195// make us blow up.
196
197function parse(buf) {
19861 var pkt, oid, bvb, vb, hdr;
199
20061 pkt = new Packet();
201
202 // First we have a sequence marker (two bytes).
203 // We don't care about those, so cut them off.
20461 hdr = asn1ber.typeAndLength(buf);
20561 assert.equal(asn1ber.types.Sequence, hdr.type);
20659 buf = buf.slice(hdr.header);
207
208 // Then comes the version field (integer). Parse it and slice it.
20959 pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
21059 buf = buf.slice(2 + buf[1]);
211
212 // We then get the community. Parse and slice.
21359 pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2));
21459 buf = buf.slice(2 + buf[1]);
215
216 // Here's the PDU structure. We're interested in the type. Slice the rest.
21759 hdr = asn1ber.typeAndLength(buf);
21859 assert.ok(hdr.type >= 0xA0);
21959 pkt.pdu.type = hdr.type - 0xA0;
22059 buf = buf.slice(hdr.header);
221
222 // The request id field.
22359 pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
22459 buf = buf.slice(2 + buf[1]);
225
226 // The error field.
22759 pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
22859 buf = buf.slice(2 + buf[1]);
229
230 // The error index field.
23159 pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
23259 buf = buf.slice(2 + buf[1]);
233
234 // Here's the varbind list. Not interested.
23559 hdr = asn1ber.typeAndLength(buf);
23659 assert.equal(asn1ber.types.Sequence, hdr.type);
23759 buf = buf.slice(hdr.header);
238
239 // Now comes the varbinds. There might be many, so we loop for as long as we have data.
24059 pkt.pdu.varbinds = [];
24159 while (buf[0] === asn1ber.types.Sequence) {
24285 vb = new VarBind();
243
244 // Slice off the sequence header.
24585 hdr = asn1ber.typeAndLength(buf);
24685 assert.equal(asn1ber.types.Sequence, hdr.type);
24785 bvb = buf.slice(hdr.header);
248
249 // Parse and save the ObjectIdentifier.
25085 vb.oid = asn1ber.parseOid(bvb);
251
252 // Parse the value. We use the type marker to figure out
253 // what kind of value it is and call the appropriate parser
254 // routine. For the SNMPv2c error types, we simply set the
255 // value to a text representation of the error and leave handling
256 // up to the user.
25785 bvb = bvb.slice(2 + bvb[1]);
25885 vb.type = bvb[0];
25985 if (vb.type === asn1ber.types.Null) {
260 // Null type.
26116 vb.value = null;
26269 } else if (vb.type === asn1ber.types.OctetString) {
263 // Octet string type.
26416 vb.value = asn1ber.parseOctetString(bvb);
26553 } else if (vb.type === asn1ber.types.Integer ||
266 vb.type === asn1ber.types.Counter ||
267 vb.type === asn1ber.types.Counter64 ||
268 vb.type === asn1ber.types.TimeTicks ||
269 vb.type === asn1ber.types.Gauge) {
270 // Integer type and it's derivatives that behave in the same manner.
27141 vb.value = asn1ber.parseInteger(bvb);
27212 } else if (vb.type === asn1ber.types.ObjectIdentifier) {
273 // Object identifier type.
2741 vb.value = asn1ber.parseOid(bvb);
27511 } else if (vb.type === asn1ber.types.IpAddress) {
276 // IP Address type.
2771 vb.value = asn1ber.parseArray(bvb);
27810 } else if (vb.type === asn1ber.types.Opaque) {
279 // Opaque type. The 'parsing' here is very light; basically we return a
280 // string representation of the raw bytes in hex.
2812 vb.value = asn1ber.parseOpaque(bvb);
2828 } else if (vb.type === asn1ber.types.EndOfMibView) {
283 // End of MIB view error, returned when attempting to GetNext beyond the end
284 // of the current view.
2851 vb.value = 'endOfMibView';
2867 } else if (vb.type === asn1ber.types.NoSuchObject) {
287 // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist.
2881 vb.value = 'noSuchObject';
2896 } else if (vb.type === asn1ber.types.NoSuchInstance) {
290 // No such instance error, returned when attempting to Get/GetNext an instance
291 // that doesn't exist in a given table.
2926 vb.value = 'noSuchInstance';
293 } else {
294 // Something else that we can't handle, so throw an error.
295 // The error will be caught and presented in a useful manner on stderr,
296 // with a dump of the message causing it.
2970 throw new Error('Unrecognized value type ' + vb.type);
298 }
299
300 // Take the raw octet string value and preseve it as a buffer and hex string.
30185 vb.valueRaw = bvb.slice(2);
30285 vb.valueHex = vb.valueRaw.toString('hex');
303
304 // Add the request id to the varbind (even though it doesn't really belong)
305 // so that it will be availble to the end user.
30685 vb.requestId = pkt.pdu.reqid;
307
308 // Push whatever we parsed to the varbind list.
30985 pkt.pdu.varbinds.push(vb);
310
311 // Go fetch the next varbind, if there seems to be any.
31285 if (buf.length > hdr.header + hdr.len) {
31326 buf = buf.slice(hdr.header + hdr.len);
314 } else {
31559 break;
316 }
317 }
318
31959 return pkt;
320}
321
3221exports.parse = parse;
323
324// Utility functions
325// -----
326
327// Compare two OIDs, returning -1, 0 or +1 depending on the relation between
328// oidA and oidB.
329
3301exports.compareOids = function (oidA, oidB) {
3319 var mlen, i;
332
333 // The undefined OID, if there is any, is deemed lesser.
3349 if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') {
3351 return 1;
3368 } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') {
3371 return -1;
338 }
339
340 // Check each number part of the OIDs individually, and if there is any
341 // position where one OID is larger than the other, return accordingly.
342 // This will only check up to the minimum length of both OIDs.
3437 mlen = Math.min(oidA.length, oidB.length);
3447 for (i = 0; i < mlen; i++) {
34526 if (oidA[i] > oidB[i]) {
3461 return -1;
34725 } else if (oidB[i] > oidA[i]) {
3481 return 1;
349 }
350 }
351
352 // If there is one OID that is longer than the other after the above comparison,
353 // consider the shorter OID to be lesser.
3545 if (oidA.length > oidB.length) {
3552 return -1;
3563 } else if (oidB.length > oidA.length) {
3572 return 1;
358 } else {
359 // The OIDs are obviously equal.
3601 return 0;
361 }
362};
363
364
365// Communication functions
366// -----
367
368// This is called for when we receive a message.
369
370function msgReceived(msg, rinfo) {
37144 var self = this, now = Date.now(), pkt, entry;
372
37344 if (msg.length === 0) {
374 // Not sure why we sometimes receive an empty message.
375 // As far as I'm concerned it shouldn't happen, but we'll ignore it
376 // and if it's necessary a retransmission of the request will be
377 // made later.
3780 return;
379 }
380
381 // Parse the packet, or call the informative
382 // parse error display if we fail.
38344 try {
38444 pkt = parse(msg);
385 } catch (error) {
3861 return self.parseError(error, msg);
387 }
388
389 // If this message's request id matches one we've sent,
390 // cancel any outstanding timeout and call the registered
391 // callback.
39243 entry = self.reqs[pkt.pdu.reqid];
39343 if (entry) {
39440 clearRequest(self.reqs, pkt.pdu.reqid);
395
39640 if (typeof entry.callback === 'function') {
39739 pkt.pdu.varbinds.forEach(function (vb) {
39865 vb.receiveStamp = now;
39965 vb.sendStamp = entry.sendStamp;
400 });
401
40239 entry.callback(null, pkt.pdu.varbinds);
403 }
404 } else {
405 // This happens if we receive the response to a message we've already timed out
406 // and removed the request entry for. Maybe we shouldn't even log the warning.
407
408 // Calculate the approximate send time and how old the packet is.
4093 var age = (Date.now() & 0x1fffff) - (pkt.pdu.reqid >>> 10);
4103 if (age < 0) {
4110 age += 0x200000;
412 }
4133 console.warn('Response with unknown request ID from ' + rinfo.address + '. Consider increasing timeouts (' + age + ' ms old?).');
414 }
415}
416
417// Default options for new sessions and operations.
4181exports.defaultOptions = {
419 host: 'localhost',
420 port: 161,
421 community: 'public',
422 family: 'udp4',
423 timeouts: [ 5000, 5000, 5000, 5000 ]
424};
425
426// This creates a new SNMP session.
427
428function Session(options) {
42947 var self = this;
430
43147 self.options = options || {};
43247 defaults(self.options, exports.defaultOptions);
433
43447 self.reqs = {};
43547 self.socket = dgram.createSocket(self.options.family);
43647 self.socket.on('message', msgReceived.bind(self));
43747 self.socket.on('close', function () {
438 // Remove the socket so we don't try to send a message on
439 // it when it's closed.
4400 self.socket = undefined;
441 });
44247 self.socket.on('error', function () {
443 // Errors will be emitted here as well as on the callback to the send function.
444 // We handle them there, so doing anything here is unnecessary.
445 // But having no error handler trips up the test suite.
446 });
447}
448
449// We inherit from EventEmitter so that we can emit error events
450// on fatal errors.
4511Session.prototype = Object.create(events.EventEmitter.prototype);
4521exports.Session = Session;
453
454// Generate a request ID. It's best kept within a signed 32 bit integer.
455// Uses the current time in ms, shifted left ten bits, plus a counter.
456// This gives us space for 1 transmit every microsecond and wraps every
457// ~1000 seconds. This is OK since we only need to keep unique ID:s for in
458// flight packets and they should be safely timed out by then.
459
4601Session.prototype.requestId = function () {
46145 var self = this, now = Date.now();
462
46345 if (!self.prevTs) {
46435 self.prevTs = now;
46535 self.counter = 0;
466 }
467
46845 if (now === self.prevTs) {
46935 self.counter += 1;
47035 if (self.counter > 1023) {
4710 throw new Error('Request ID counter overflow. Adjust algorithm.');
472 }
473 } else {
47410 self.prevTs = now;
47510 self.counter = 0;
476 }
477
47845 return ((now & 0x1fffff) << 10) + self.counter;
479};
480
481// Display useful debugging information when a parse error occurs.
482
4831Session.prototype.parseError = function (error, buffer) {
4841 var self = this, hex;
485
486 // Display a friendly introductory text.
4871 console.error('Woops! An error occurred while parsing an SNMP message. :(');
4881 console.error('To have this problem corrected, please report the information below verbatim');
4891 console.error('via email to snmp@nym.se or by creating a GitHub issue at');
4901 console.error('https://github.com/calmh/node-snmp-native/issues');
4911 console.error('');
4921 console.error('Thanks!');
493
494 // Display the stack backtrace so we know where the exception happened.
4951 console.error('');
4961 console.error(error.stack);
497
498 // Display the buffer data, nicely formatted so we can replicate the problem.
4991 console.error('\nMessage data:');
5001 hex = buffer.toString('hex');
5011 while (hex.length > 0) {
5027 console.error(' ' + hex.slice(0, 32).replace(/([0-9a-f]{2})/g, '$1 '));
5037 hex = hex.slice(32);
504 }
505
506 // Let the exception bubble upwards.
5071 self.emit('error', error);
508};
509
510// Send a message. Can be used after manually constructing a correct Packet structure.
511
5121Session.prototype.sendMsg = function (pkt, options, callback) {
51345 var self = this, buf, reqid, retrans = 0;
514
51545 defaults(options, self.options);
516
51745 reqid = self.requestId();
51845 pkt.pdu.reqid = reqid;
519
52045 buf = encode(pkt);
521
522 function transmit() {
52347 if (!self.socket || !self.reqs[reqid]) {
524 // The socket has already been closed, perhaps due to an error that ocurred while a timeout
525 // was scheduled. We can't do anything about it now.
5260 clearRequest(self.reqs, reqid);
5270 return;
528 }
529
530 // Send the message.
53147 self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) {
53247 var entry = self.reqs[reqid];
533
53447 if (err) {
5351 clearRequest(self.reqs, reqid);
5361 return callback(err);
53746 } else if (entry) {
53846 entry.sendStamp = Date.now();
539
54046 if (options.timeouts[retrans]) {
541 // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply.
54244 entry.timeout = setTimeout(transmit, options.timeouts[retrans]);
54344 retrans += 1;
544 } else {
5452 clearRequest(self.reqs, reqid);
5462 return callback(new Error('Timeout'));
547 }
548 }
549 });
550 }
551
552 // Register the callback to call when we receive a reply.
55344 self.reqs[reqid] = { callback: callback };
554 // Transmit the message.
55544 transmit();
556};
557
558// Shortcut to create a GetRequest and send it, while registering a callback.
559// Needs `options.oid` to be an OID in array form.
560
5611Session.prototype.get = function (options, callback) {
56224 var self = this, pkt;
563
56424 defaults(options, self.options);
56524 parseOids(options);
566
56723 if (!options.oid) {
5681 return callback(null, []);
569 }
570
57122 pkt = new Packet();
57222 pkt.community = options.community;
57322 pkt.pdu.varbinds[0].oid = options.oid;
57422 self.sendMsg(pkt, options, callback);
575};
576
577// Shortcut to create a SetRequest and send it, while registering a callback.
578// Needs `options.oid` to be an OID in array form, `options.value` to be an
579// integer and `options.type` to be asn1ber.T.Integer (2).
580
5811Session.prototype.set = function (options, callback) {
58210 var self = this, pkt;
583
58410 defaults(options, self.options);
58510 parseOids(options);
586
5879 if (!options.oid) {
5881 throw new Error('Missing required option `oid`.');
5898 } else if (options.value === undefined) {
5901 throw new Error('Missing required option `value`.');
5917 } else if (!options.type) {
5921 throw new Error('Missing required option `type`.');
593 }
594
5956 pkt = new Packet();
5966 pkt.community = options.community;
5976 pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU;
5986 pkt.pdu.varbinds[0].oid = options.oid;
5996 pkt.pdu.varbinds[0].type = options.type;
6006 pkt.pdu.varbinds[0].value = options.value;
6016 self.sendMsg(pkt, options, callback);
602};
603
604// Shortcut to get all OIDs in the `options.oids` array sequentially. The
605// callback is called when the entire operation is completed. If
606// options.abortOnError is truish, an error while getting any of the values
607// will cause the callback to be called with error status. When
608// `options.abortOnError` is falsish (the default), any errors will be ignored
609// and any successfully retrieved values sent to the callback.
610
6111Session.prototype.getAll = function (options, callback) {
6125 var self = this, results = [];
613
6145 defaults(options, self.options, { abortOnError: false });
6155 parseOids(options);
616
6175 if (!options.oids || options.oids.length === 0) {
6182 return callback(null, []);
619 }
620
621 function getOne(c) {
6224 var oid, pkt, m, vb;
623
6244 pkt = new Packet();
6254 pkt.community = options.community;
6264 pkt.pdu.varbinds = [];
627
628 // Push up to 16 varbinds in the same message.
629 // The number 16 isn't really that magical, it's just a nice round
630 // number that usually seems to fit withing a single packet and gets
631 // accepted by the switches I've tested it on.
6324 for (m = 0; m < 16 && c < options.oids.length; m++) {
63330 vb = new VarBind();
63430 vb.oid = options.oids[c];
63530 pkt.pdu.varbinds.push(vb);
63630 c++;
637 }
638
6394 self.sendMsg(pkt, options, function (err, varbinds) {
6404 if (options.abortOnError && err) {
6410 callback(err);
642 } else {
6434 if (varbinds) {
6444 results = results.concat(varbinds);
645 }
6464 if (c < options.oids.length) {
6471 getOne(c);
648 } else {
6493 callback(null, results);
650 }
651 }
652 });
653 }
654
6553 getOne(0);
656};
657
658// Shortcut to create a GetNextRequest and send it, while registering a callback.
659// Needs `options.oid` to be an OID in array form.
660
6611Session.prototype.getNext = function (options, callback) {
66216 var self = this, pkt;
663
66416 defaults(options, self.options);
66516 parseOids(options);
666
66714 if (!options.oid) {
6681 return callback(null, []);
669 }
670
67113 pkt = new Packet();
67213 pkt.community = options.community;
67313 pkt.pdu.type = 1;
67413 pkt.pdu.varbinds[0].oid = options.oid;
67513 self.sendMsg(pkt, options, callback);
676};
677
678// Shortcut to get all entries below the specified OID.
679// The callback will be called once with the list of
680// varbinds that was collected, or with an error object.
681// Needs `options.oid` to be an OID in array form.
682
6831Session.prototype.getSubtree = function (options, callback) {
6843 var self = this, vbs = [];
685
6863 defaults(options, self.options);
6873 parseOids(options);
688
6893 if (!options.oid) {
6901 return callback(null, []);
691 }
692
6932 options.startOid = options.oid;
694
695 // Helper to check whether `oid` in inside the tree rooted at
696 // `root` or not.
697 function inTree(root, oid) {
69811 var i;
69911 if (oid.length <= root.length) {
7001 return false;
701 }
70210 for (i = 0; i < root.length; i++) {
70379 if (oid[i] !== root[i]) {
7041 return false;
705 }
706 }
7079 return true;
708 }
709
710 // Helper to handle the result of getNext and call the user's callback
711 // as appropriate. The callback will see one of the following patterns:
712 // - callback([an Error object], undefined) -- an error ocurred.
713 // - callback(null, [a Packet object]) -- data from under the tree.
714 // - callback(null, null) -- end of tree.
715 function result(error, varbinds) {
71611 if (error) {
7170 callback(error);
718 } else {
71911 if (inTree(options.startOid, varbinds[0].oid)) {
7209 if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') {
7210 callback(null, vbs);
722 } else {
7239 vbs.push(varbinds[0]);
7249 var next = { oid: varbinds[0].oid };
7259 defaults(next, options);
7269 self.getNext(next, result);
727 }
728 } else {
7292 callback(null, vbs);
730 }
731 }
732 }
733
7342 self.getNext(options, result);
735};
736
737// Close the socket. Necessary to finish the event loop and exit the program.
738
7391Session.prototype.close = function () {
7400 this.socket.close();
741};
742