# Coverage

96%
473
457
16

## lib/asn1ber.js

98%
156
153
3

Line | Hits | Source
--- | --- | ---
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 |  |
11 | 1 | var 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 |  |
30 | 1 | var 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 |  |
39 | 1 | exports.types = T;
40 | 1 | exports.pduTypes = P;
41 | 1 | exports.unittest = {};
42 |  |
43 |  | // Private helper functions
44 |  | // -----
45 |  |
46 |  | // Encode a length as it should be encoded.
47 |  |
48 |  | function lengthArray(len) {
49 | 345 | var arr = [];
50 |  |
51 | 345 | if (len <= 127) {
52 |  | // Return a single byte if the value is 127 or less.
53 | 339 | return [ len ];
54 |  | } else {
55 |  | // Otherwise encode it as a MSB base-256 integer.
56 | 6 | while (len > 0) {
57 | 8 | arr.push(len % 256);
58 | 8 | 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.
62 | 6 | arr.push(128 + arr.length);
63 | 6 | arr.reverse();
64 | 6 | return arr;
65 |  | }
66 |  | }
67 |  |
68 | 1 | exports.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 |  |
73 |  | function wrapper(type, contents) {
74 | 266 | var buf, len, i;
75 |  |
76 |  | // Get the encoded length of the contents
77 | 266 | len = lengthArray(contents.length);
78 |  |
79 |  | // Set up a buffer with the type and length bytes plus a straight copy of the content.
80 | 266 | buf = new Buffer(1 + contents.length + len.length);
81 | 266 | buf[0] = type;
82 | 266 | for (i = 1; i < len.length + 1; i++) {
83 | 271 | buf[i] = len[i - 1];
84 |  | }
85 | 266 | contents.copy(buf, len.length + 1, 0);
86 | 266 | 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 |  |
96 |  | function oidInt(val) {
97 | 3 | var bytes = [];
98 |  |
99 | 3 | bytes.push(val % 128);
100 | 3 | val = parseInt(val / 128, 10);
101 | 3 | while (val > 127) {
102 | 1 | bytes.push(128 + val % 128);
103 | 1 | val = parseInt(val / 128, 10);
104 |  | }
105 | 3 | bytes.push(val + 128);
106 | 3 | 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 |  |
114 |  | function oidArray(oid) {
115 | 76 | var bytes, i, val;
116 |  |
117 |  | // Enforce some minimum requirements on the OID.
118 | 76 | if (oid.length < 2) {
119 | 1 | throw new Error("Minimum OID length is two.");
120 | 75 | } else if (oid[0] !== 1 || oid[1] !== 3) {
121 | 1 | 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.
125 | 74 | 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.
129 | 74 | for (i = 2; i < oid.length; i++) {
130 | 511 | val = oid[i];
131 | 511 | if (val > 127) {
132 | 3 | bytes = bytes.concat(oidInt(val));
133 |  | } else {
134 | 508 | bytes.push(val);
135 |  | }
136 |  | }
137 |  |
138 | 74 | return bytes;
139 |  | }
140 |  |
141 |  | // Divide an integer into base-256 bytes.
142 |  | // Most significant byte first.
143 |  | function intArray(val) {
144 | 198 | var array = [];
145 |  |
146 | 198 | if (val === 0) {
147 | 94 | array.push(0);
148 |  | } else {
149 | 104 | while (val > 0) {
150 | 246 | array.push(val % 256);
151 | 246 | val = parseInt(val / 256, 10);
152 |  | }
153 |  | }
154 |  |
155 |  | // Do not produce integers that look negative (high bit
156 |  | // of first byte set).
157 | 198 | if (array[array.length - 1] >= 0x80) {
158 | 2 | array.push(0);
159 |  | }
160 |  |
161 | 198 | 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 |  |
172 | 1 | exports.encodeInteger = function (val) {
173 | 198 | var i, arr, buf;
174 |  |
175 |  | // Get the bytes that we're going to encode.
176 | 198 | 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.
180 | 198 | buf = new Buffer(2 + arr.length);
181 | 198 | buf[0] = T.Integer;
182 | 198 | buf[1] = arr.length;
183 |  |
184 |  | // Copy the bytes into the array.
185 | 198 | for (i = 0; i < arr.length; i++) {
186 | 342 | buf[i + 2] = arr[i];
187 |  | }
188 |  |
189 | 198 | return buf;
190 |  | };
191 |  |
192 |  | // Create the representation of a Null, `05 00`.
193 |  |
194 | 1 | exports.encodeNull = function () {
195 | 67 | var buf = new Buffer(2);
196 | 67 | buf[0] = T.Null;
197 | 67 | buf[1] = 0;
198 | 67 | return buf;
199 |  | };
200 |  |
201 |  | // Encode a Sequence, which is a wrapper of type `30`.
202 |  |
203 | 1 | exports.encodeSequence = function (contents) {
204 | 168 | return wrapper(T.Sequence, contents);
205 |  | };
206 |  |
207 |  | // Encode an OctetString, which is a wrapper of type `04`.
208 |  |
209 | 1 | exports.encodeOctetString = function (string) {
210 | 52 | var buf, contents;
211 |  |
212 | 52 | if (typeof string === 'string') {
213 | 49 | contents = new Buffer(string);
214 | 3 | } else if (Buffer.isBuffer(string)) {
215 | 1 | contents = string;
216 |  | } else {
217 | 2 | throw new Error('Only Buffer and string types are acceptable as OctetString.');
218 |  | }
219 |  |
220 | 50 | return wrapper(T.OctetString, contents);
221 |  | };
222 |  |
223 |  | // Encode an IpAddress, which is a wrapper of type `40`.
224 |  |
225 | 1 | exports.encodeIpAddress = function (address) {
226 | 1 | var contents, octets, value = [];
227 |  |
228 | 1 | if (typeof address !== 'string' && !Buffer.isBuffer(address)) {
229 | 0 | 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 |  |
235 | 1 | octets = address.toString().split('.');
236 | 1 | if (octets.length !== 4) {
237 | 0 | throw new Error('IP Addresses must be specified in dotted decimal format.');
238 |  | }
239 | 1 | octets.forEach(function (octet) {
240 | 4 | var octetValue = parseInt(octet, 10);
241 | 4 | if (octet < 0 || octet > 255) {
242 | 0 | throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets));
243 |  | }
244 | 4 | value.push(octetValue);
245 |  | });
246 |  |
247 | 1 | contents = new Buffer(value);
248 |  |
249 | 1 | return wrapper(T.IpAddress, contents);
250 |  | };
251 |  |
252 |  | // Encode an ObjectId.
253 |  |
254 | 1 | exports.encodeOid = function (oid) {
255 | 76 | var buf, bytes, i, len;
256 |  |
257 |  | // Get the encoded format of the OID.
258 | 76 | bytes = oidArray(oid);
259 |  |
260 |  | // Get the encoded format of the length
261 | 74 | len = lengthArray(bytes.length);
262 |  |
263 |  | // Fill in the buffer with type, length and OID data.
264 | 74 | buf = new Buffer(1 + bytes.length + len.length);
265 | 74 | buf[0] = T.ObjectIdentifier;
266 | 74 | for (i = 1; i < len.length + 1; i++) {
267 | 74 | buf[i] = len[i - 1];
268 |  | }
269 | 74 | for (i = len.length + 1; i < bytes.length + len.length + 1; i++) {
270 | 589 | buf[i] = bytes[i - len.length - 1];
271 |  | }
272 |  |
273 | 74 | return buf;
274 |  | };
275 |  |
276 |  | // Encode an SNMP request with specified `contents`.
277 |  | // The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`.
278 |  |
279 | 1 | exports.encodeRequest = function (type, contents) {
280 | 47 | 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.
287 |  | function typeAndLength(buf) {
288 | 268 | var res, len, i;
289 |  |
290 | 268 | res = { type: buf[0], len: 0, header: 1 };
291 | 268 | if (buf[1] < 128) {
292 |  | // If bit 8 is zero, this byte indicates the content length (up to 127 bytes).
293 | 261 | res.len = buf[1];
294 | 261 | 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.
298 | 7 | for (i = 0; i < buf[1] - 128; i++) {
299 | 10 | res.len += buf[i + 1];
300 | 10 | res.len *= 256;
301 |  | }
302 | 7 | res.header += buf[1] - 128 + 1;
303 |  | }
304 | 268 | return res;
305 |  | }
306 |  |
307 | 1 | exports.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 |  |
312 | 1 | exports.parseInteger = function (buf) {
313 | 281 | var i, val;
314 |  |
315 | 281 | if (buf[0] !== T.Integer && buf[0] !== T.Counter &&
316 |  | buf[0] !== T.Counter64 && buf[0] !== T.Gauge &&
317 |  | buf[0] !== T.TimeTicks) {
318 | 1 | throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer');
319 |  | }
320 |  |
321 | 280 | val = 0;
322 | 280 | for (i = 0; i < buf[1]; i++) {
323 | 547 | val *= 256;
324 | 547 | val += buf[i + 2];
325 |  | }
326 |  |
327 | 280 | 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 |  |
333 | 1 | exports.parseOctetString = function (buf) {
334 | 78 | if (buf[0] !== T.OctetString) {
335 | 1 | 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.
340 | 77 | 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 |  |
346 | 1 | exports.parseOid = function (buf) {
347 | 90 | var oid, val, i;
348 |  |
349 | 90 | if (buf[0] !== T.ObjectIdentifier) {
350 | 1 | 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
354 | 89 | oid = [ parseInt(buf[2] / 40, 10), buf[2] % 40 ];
355 |  |
356 |  | // The rest of the data is a base-128-encoded OID
357 | 89 | for (i = 0; i < buf[1] - 1; i++) {
358 | 681 | val = 0;
359 | 681 | while (buf[i + 3] >= 128) {
360 | 17 | val += buf[i + 3] - 128;
361 | 17 | val *= 128;
362 | 17 | i++;
363 |  | }
364 | 681 | val += buf[i + 3];
365 | 681 | oid.push(val);
366 |  | }
367 |  |
368 | 89 | return oid;
369 |  | };
370 |  |
371 |  | // Parse a buffer containing a representation of an array type.
372 |  | // This is for example an IpAddress.
373 |  |
374 | 1 | exports.parseArray = function (buf) {
375 | 3 | var i, nelem, array;
376 |  |
377 | 3 | if (buf[0] !== T.IpAddress) {
378 | 1 | throw new Error('Buffer does not appear to be an array type.');
379 |  | }
380 |  |
381 | 2 | nelem = buf[1];
382 | 2 | array = [];
383 |  |
384 | 2 | for (i = 0; i < buf[1]; i++) {
385 | 8 | array.push(buf[i + 2]);
386 |  | }
387 |  |
388 | 2 | return array;
389 |  | };
390 |  |
391 |  | // Parse a buffer containing a representation of an opaque type.
392 |  | // This is for example an IpAddress.
393 |  |
394 | 1 | exports.parseOpaque = function (buf) {
395 | 4 | var hdr;
396 |  |
397 | 4 | hdr = typeAndLength(buf);
398 |  |
399 | 4 | if (hdr.type !== T.Opaque) {
400 | 1 | throw new Error('Buffer does not appear to be an opaque type.');
401 |  | }
402 |  |
403 | 3 | return '0x' + buf.slice(hdr.header).toString('hex');
404 |  | };
405 |  |
406 |  | /*globals exports: false*/
407 |  |

## lib/snmp.js

95%
317
304
13

Line | Hits | Source
--- | --- | ---
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.
17 | 1 | var assert = require('assert');
18 | 1 | var dgram = require('dgram');
19 | 1 | var events = require('events');
20 |  |
21 |  | // We also need our ASN.1 BER en-/decoding routines.
22 | 1 | var asn1ber = require('./asn1ber');
23 |  |
24 |  | // Basic structures
25 |  | // ----
26 |  |
27 |  | // A `VarBind` is the innermost structure, containing an OID-Value pair.
28 |  | function VarBind() {
29 | 223 | this.type = 5;
30 | 223 | this.value = null;
31 |  | }
32 |  |
33 |  | // The `PDU` contains the SNMP request or response fields and a list of `VarBinds`.
34 |  | function PDU() {
35 | 108 | this.type = asn1ber.pduTypes.GetRequestPDU;
36 | 108 | this.reqid = 1;
37 | 108 | this.error = 0;
38 | 108 | this.errorIndex = 0;
39 | 108 | this.varbinds = [ new VarBind() ];
40 |  | }
41 |  |
42 |  | // The `Packet` contains the SNMP version and community and the `PDU`.
43 |  | function Packet() {
44 | 108 | this.version = 1;
45 | 108 | this.community = 'public';
46 | 108 | this.pdu = new PDU();
47 |  | }
48 |  |
49 |  | // Allow consumers to create packet structures from scratch.
50 | 1 | exports.Packet = Packet;
51 |  |
52 |  | // Private helper functions
53 |  | // ----
54 |  |
55 |  | // Concatenate several buffers to one.
56 |  | function concatBuffers(buffers) {
57 | 210 | var total, cur = 0, buf;
58 |  |
59 |  | // First we calculate the total length,
60 | 210 | total = buffers.reduce(function (tot, b) {
61 | 538 | return tot + b.length;
62 |  | }, 0);
63 |  |
64 |  | // then we allocate a new Buffer large enough to contain all data,
65 | 210 | buf = new Buffer(total);
66 | 210 | buffers.forEach(function (buffer) {
67 |  | // finally we copy the data into the new larger buffer.
68 | 538 | buffer.copy(buf, cur, 0);
69 | 538 | cur += buffer.length;
70 |  | });
71 |  |
72 | 210 | return buf;
73 |  | }
74 |  |
75 |  | // Clear a pending packet when it times out or is successfully received.
76 |  |
77 |  | function clearRequest(reqs, reqid) {
78 | 43 | var self = this;
79 |  |
80 | 43 | var entry = reqs[reqid];
81 | 43 | if (entry) {
82 | 43 | if (entry.timeout) {
83 | 42 | clearTimeout(entry.timeout);
84 |  | }
85 | 43 | delete reqs[reqid];
86 |  | }
87 |  | }
88 |  |
89 |  | // Convert a string formatted OID to an array, leaving anything non-string alone.
90 |  |
91 |  | function parseSingleOid(oid) {
92 | 79 | if (typeof oid !== 'string') {
93 | 59 | return oid;
94 |  | }
95 |  |
96 | 20 | if (oid[0] !== '.') {
97 | 4 | throw new Error('Invalid OID format');
98 |  | }
99 |  |
100 | 16 | oid = oid.split('.')
101 |  | .filter(function (s) {
102 | 154 | return s.length > 0;
103 |  | })
104 |  | .map(function (s) {
105 | 138 | return parseInt(s, 10);
106 |  | });
107 |  |
108 | 16 | return oid;
109 |  | }
110 |  |
111 |  | // Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings.
112 |  |
113 |  | function parseOids(options) {
114 | 58 | if (options.oid) {
115 | 48 | options.oid = parseSingleOid(options.oid);
116 |  | }
117 | 55 | if (options.oids) {
118 | 5 | 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 |  |
125 |  | function defaults(targ, _defs) {
126 | 159 | [].slice.call(arguments, 1).forEach(function (def) {
127 | 164 | Object.keys(def).forEach(function (key) {
128 | 818 | if (!targ.hasOwnProperty(key)) {
129 | 468 | 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.
140 |  | function encode(pkt) {
141 | 47 | var version, community, reqid, err, erridx, vbs, pdu, message;
142 |  |
143 |  | // We only support SNMPv2c, so enforce that version stamp.
144 | 47 | if (pkt.version !== 1) {
145 | 0 | throw new Error('Only SNMPv2c is supported.');
146 |  | }
147 |  |
148 |  | // Encode the message header fields.
149 | 47 | version = asn1ber.encodeInteger(pkt.version);
150 | 47 | community = asn1ber.encodeOctetString(pkt.community);
151 |  |
152 |  | // Encode the PDU header fields.
153 | 47 | reqid = asn1ber.encodeInteger(pkt.pdu.reqid);
154 | 47 | err = asn1ber.encodeInteger(pkt.pdu.error);
155 | 47 | erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex);
156 |  |
157 |  | // Encode the PDU varbinds.
158 | 47 | vbs = [];
159 | 47 | pkt.pdu.varbinds.forEach(function (vb) {
160 | 73 | var oid = asn1ber.encodeOid(vb.oid), val;
161 |  |
162 | 73 | if (vb.type === asn1ber.types.Null) {
163 | 66 | val = asn1ber.encodeNull();
164 | 7 | } else if (vb.type === asn1ber.types.Integer) {
165 | 5 | val = asn1ber.encodeInteger(vb.value);
166 | 2 | } else if (vb.type === asn1ber.types.IpAddress) {
167 | 1 | val = asn1ber.encodeIpAddress(vb.value);
168 | 1 | } else if (vb.type === asn1ber.types.OctetString) {
169 | 1 | val = asn1ber.encodeOctetString(vb.value);
170 |  | } else {
171 | 0 | throw new Error('Unknown varbind type "' + vb.type + '" in encoding.');
172 |  | }
173 | 72 | vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val])));
174 |  | });
175 |  |
176 |  | // Concatenate all the varbinds together.
177 | 46 | vbs = asn1ber.encodeSequence(concatBuffers(vbs));
178 |  |
179 |  | // Create the PDU by concatenating the inner fields and adding a request structure around it.
180 | 46 | pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs]));
181 |  |
182 |  | // Create the message by concatenating the header fields and the PDU.
183 | 46 | message = asn1ber.encodeSequence(concatBuffers([version, community, pdu]));
184 |  |
185 | 46 | return message;
186 |  | }
187 |  |
188 | 1 | exports.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 |  |
197 |  | function parse(buf) {
198 | 61 | var pkt, oid, bvb, vb, hdr;
199 |  |
200 | 61 | pkt = new Packet();
201 |  |
202 |  | // First we have a sequence marker (two bytes).
203 |  | // We don't care about those, so cut them off.
204 | 61 | hdr = asn1ber.typeAndLength(buf);
205 | 61 | assert.equal(asn1ber.types.Sequence, hdr.type);
206 | 59 | buf = buf.slice(hdr.header);
207 |  |
208 |  | // Then comes the version field (integer). Parse it and slice it.
209 | 59 | pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
210 | 59 | buf = buf.slice(2 + buf[1]);
211 |  |
212 |  | // We then get the community. Parse and slice.
213 | 59 | pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2));
214 | 59 | buf = buf.slice(2 + buf[1]);
215 |  |
216 |  | // Here's the PDU structure. We're interested in the type. Slice the rest.
217 | 59 | hdr = asn1ber.typeAndLength(buf);
218 | 59 | assert.ok(hdr.type >= 0xA0);
219 | 59 | pkt.pdu.type = hdr.type - 0xA0;
220 | 59 | buf = buf.slice(hdr.header);
221 |  |
222 |  | // The request id field.
223 | 59 | pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
224 | 59 | buf = buf.slice(2 + buf[1]);
225 |  |
226 |  | // The error field.
227 | 59 | pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
228 | 59 | buf = buf.slice(2 + buf[1]);
229 |  |
230 |  | // The error index field.
231 | 59 | pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
232 | 59 | buf = buf.slice(2 + buf[1]);
233 |  |
234 |  | // Here's the varbind list. Not interested.
235 | 59 | hdr = asn1ber.typeAndLength(buf);
236 | 59 | assert.equal(asn1ber.types.Sequence, hdr.type);
237 | 59 | 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.
240 | 59 | pkt.pdu.varbinds = [];
241 | 59 | while (buf[0] === asn1ber.types.Sequence) {
242 | 85 | vb = new VarBind();
243 |  |
244 |  | // Slice off the sequence header.
245 | 85 | hdr = asn1ber.typeAndLength(buf);
246 | 85 | assert.equal(asn1ber.types.Sequence, hdr.type);
247 | 85 | bvb = buf.slice(hdr.header);
248 |  |
249 |  | // Parse and save the ObjectIdentifier.
250 | 85 | 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.
257 | 85 | bvb = bvb.slice(2 + bvb[1]);
258 | 85 | vb.type = bvb[0];
259 | 85 | if (vb.type === asn1ber.types.Null) {
260 |  | // Null type.
261 | 16 | vb.value = null;
262 | 69 | } else if (vb.type === asn1ber.types.OctetString) {
263 |  | // Octet string type.
264 | 16 | vb.value = asn1ber.parseOctetString(bvb);
265 | 53 | } 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.
271 | 41 | vb.value = asn1ber.parseInteger(bvb);
272 | 12 | } else if (vb.type === asn1ber.types.ObjectIdentifier) {
273 |  | // Object identifier type.
274 | 1 | vb.value = asn1ber.parseOid(bvb);
275 | 11 | } else if (vb.type === asn1ber.types.IpAddress) {
276 |  | // IP Address type.
277 | 1 | vb.value = asn1ber.parseArray(bvb);
278 | 10 | } 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.
281 | 2 | vb.value = asn1ber.parseOpaque(bvb);
282 | 8 | } 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.
285 | 1 | vb.value = 'endOfMibView';
286 | 7 | } else if (vb.type === asn1ber.types.NoSuchObject) {
287 |  | // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist.
288 | 1 | vb.value = 'noSuchObject';
289 | 6 | } 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.
292 | 6 | 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.
297 | 0 | 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.
301 | 85 | vb.valueRaw = bvb.slice(2);
302 | 85 | 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.
306 | 85 | vb.requestId = pkt.pdu.reqid;
307 |  |
308 |  | // Push whatever we parsed to the varbind list.
309 | 85 | pkt.pdu.varbinds.push(vb);
310 |  |
311 |  | // Go fetch the next varbind, if there seems to be any.
312 | 85 | if (buf.length > hdr.header + hdr.len) {
313 | 26 | buf = buf.slice(hdr.header + hdr.len);
314 |  | } else {
315 | 59 | break;
316 |  | }
317 |  | }
318 |  |
319 | 59 | return pkt;
320 |  | }
321 |  |
322 | 1 | exports.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 |  |
330 | 1 | exports.compareOids = function (oidA, oidB) {
331 | 9 | var mlen, i;
332 |  |
333 |  | // The undefined OID, if there is any, is deemed lesser.
334 | 9 | if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') {
335 | 1 | return 1;
336 | 8 | } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') {
337 | 1 | 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.
343 | 7 | mlen = Math.min(oidA.length, oidB.length);
344 | 7 | for (i = 0; i < mlen; i++) {
345 | 26 | if (oidA[i] > oidB[i]) {
346 | 1 | return -1;
347 | 25 | } else if (oidB[i] > oidA[i]) {
348 | 1 | 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.
354 | 5 | if (oidA.length > oidB.length) {
355 | 2 | return -1;
356 | 3 | } else if (oidB.length > oidA.length) {
357 | 2 | return 1;
358 |  | } else {
359 |  | // The OIDs are obviously equal.
360 | 1 | return 0;
361 |  | }
362 |  | };
363 |  |
364 |  |
365 |  | // Communication functions
366 |  | // -----
367 |  |
368 |  | // This is called for when we receive a message.
369 |  |
370 |  | function msgReceived(msg, rinfo) {
371 | 44 | var self = this, now = Date.now(), pkt, entry;
372 |  |
373 | 44 | 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.
378 | 0 | return;
379 |  | }
380 |  |
381 |  | // Parse the packet, or call the informative
382 |  | // parse error display if we fail.
383 | 44 | try {
384 | 44 | pkt = parse(msg);
385 |  | } catch (error) {
386 | 1 | 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.
392 | 43 | entry = self.reqs[pkt.pdu.reqid];
393 | 43 | if (entry) {
394 | 40 | clearRequest(self.reqs, pkt.pdu.reqid);
395 |  |
396 | 40 | if (typeof entry.callback === 'function') {
397 | 39 | pkt.pdu.varbinds.forEach(function (vb) {
398 | 65 | vb.receiveStamp = now;
399 | 65 | vb.sendStamp = entry.sendStamp;
400 |  | });
401 |  |
402 | 39 | 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.
409 | 3 | var age = (Date.now() & 0x1fffff) - (pkt.pdu.reqid >>> 10);
410 | 3 | if (age < 0) {
411 | 0 | age += 0x200000;
412 |  | }
413 | 3 | 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.
418 | 1 | exports.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 |  |
428 |  | function Session(options) {
429 | 47 | var self = this;
430 |  |
431 | 47 | self.options = options || {};
432 | 47 | defaults(self.options, exports.defaultOptions);
433 |  |
434 | 47 | self.reqs = {};
435 | 47 | self.socket = dgram.createSocket(self.options.family);
436 | 47 | self.socket.on('message', msgReceived.bind(self));
437 | 47 | 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.
440 | 0 | self.socket = undefined;
441 |  | });
442 | 47 | 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.
451 | 1 | Session.prototype = Object.create(events.EventEmitter.prototype);
452 | 1 | exports.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 |  |
460 | 1 | Session.prototype.requestId = function () {
461 | 45 | var self = this, now = Date.now();
462 |  |
463 | 45 | if (!self.prevTs) {
464 | 35 | self.prevTs = now;
465 | 35 | self.counter = 0;
466 |  | }
467 |  |
468 | 45 | if (now === self.prevTs) {
469 | 35 | self.counter += 1;
470 | 35 | if (self.counter > 1023) {
471 | 0 | throw new Error('Request ID counter overflow. Adjust algorithm.');
472 |  | }
473 |  | } else {
474 | 10 | self.prevTs = now;
475 | 10 | self.counter = 0;
476 |  | }
477 |  |
478 | 45 | return ((now & 0x1fffff) << 10) + self.counter;
479 |  | };
480 |  |
481 |  | // Display useful debugging information when a parse error occurs.
482 |  |
483 | 1 | Session.prototype.parseError = function (error, buffer) {
484 | 1 | var self = this, hex;
485 |  |
486 |  | // Display a friendly introductory text.
487 | 1 | console.error('Woops! An error occurred while parsing an SNMP message. :(');
488 | 1 | console.error('To have this problem corrected, please report the information below verbatim');
489 | 1 | console.error('via email to snmp@nym.se or by creating a GitHub issue at');
490 | 1 | console.error('https://github.com/calmh/node-snmp-native/issues');
491 | 1 | console.error('');
492 | 1 | console.error('Thanks!');
493 |  |
494 |  | // Display the stack backtrace so we know where the exception happened.
495 | 1 | console.error('');
496 | 1 | console.error(error.stack);
497 |  |
498 |  | // Display the buffer data, nicely formatted so we can replicate the problem.
499 | 1 | console.error('\nMessage data:');
500 | 1 | hex = buffer.toString('hex');
501 | 1 | while (hex.length > 0) {
502 | 7 | console.error(' ' + hex.slice(0, 32).replace(/([0-9a-f]{2})/g, '$1 '));
503 | 7 | hex = hex.slice(32);
504 |  | }
505 |  |
506 |  | // Let the exception bubble upwards.
507 | 1 | self.emit('error', error);
508 |  | };
509 |  |
510 |  | // Send a message. Can be used after manually constructing a correct Packet structure.
511 |  |
512 | 1 | Session.prototype.sendMsg = function (pkt, options, callback) {
513 | 45 | var self = this, buf, reqid, retrans = 0;
514 |  |
515 | 45 | defaults(options, self.options);
516 |  |
517 | 45 | reqid = self.requestId();
518 | 45 | pkt.pdu.reqid = reqid;
519 |  |
520 | 45 | buf = encode(pkt);
521 |  |
522 |  | function transmit() {
523 | 47 | 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.
526 | 0 | clearRequest(self.reqs, reqid);
527 | 0 | return;
528 |  | }
529 |  |
530 |  | // Send the message.
531 | 47 | self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) {
532 | 47 | var entry = self.reqs[reqid];
533 |  |
534 | 47 | if (err) {
535 | 1 | clearRequest(self.reqs, reqid);
536 | 1 | return callback(err);
537 | 46 | } else if (entry) {
538 | 46 | entry.sendStamp = Date.now();
539 |  |
540 | 46 | if (options.timeouts[retrans]) {
541 |  | // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply.
542 | 44 | entry.timeout = setTimeout(transmit, options.timeouts[retrans]);
543 | 44 | retrans += 1;
544 |  | } else {
545 | 2 | clearRequest(self.reqs, reqid);
546 | 2 | return callback(new Error('Timeout'));
547 |  | }
548 |  | }
549 |  | });
550 |  | }
551 |  |
552 |  | // Register the callback to call when we receive a reply.
553 | 44 | self.reqs[reqid] = { callback: callback };
554 |  | // Transmit the message.
555 | 44 | 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 |  |
561 | 1 | Session.prototype.get = function (options, callback) {
562 | 24 | var self = this, pkt;
563 |  |
564 | 24 | defaults(options, self.options);
565 | 24 | parseOids(options);
566 |  |
567 | 23 | if (!options.oid) {
568 | 1 | return callback(null, []);
569 |  | }
570 |  |
571 | 22 | pkt = new Packet();
572 | 22 | pkt.community = options.community;
573 | 22 | pkt.pdu.varbinds[0].oid = options.oid;
574 | 22 | 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 |  |
581 | 1 | Session.prototype.set = function (options, callback) {
582 | 10 | var self = this, pkt;
583 |  |
584 | 10 | defaults(options, self.options);
585 | 10 | parseOids(options);
586 |  |
587 | 9 | if (!options.oid) {
588 | 1 | throw new Error('Missing required option `oid`.');
589 | 8 | } else if (options.value === undefined) {
590 | 1 | throw new Error('Missing required option `value`.');
591 | 7 | } else if (!options.type) {
592 | 1 | throw new Error('Missing required option `type`.');
593 |  | }
594 |  |
595 | 6 | pkt = new Packet();
596 | 6 | pkt.community = options.community;
597 | 6 | pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU;
598 | 6 | pkt.pdu.varbinds[0].oid = options.oid;
599 | 6 | pkt.pdu.varbinds[0].type = options.type;
600 | 6 | pkt.pdu.varbinds[0].value = options.value;
601 | 6 | 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 |  |
611 | 1 | Session.prototype.getAll = function (options, callback) {
612 | 5 | var self = this, results = [];
613 |  |
614 | 5 | defaults(options, self.options, { abortOnError: false });
615 | 5 | parseOids(options);
616 |  |
617 | 5 | if (!options.oids || options.oids.length === 0) {
618 | 2 | return callback(null, []);
619 |  | }
620 |  |
621 |  | function getOne(c) {
622 | 4 | var oid, pkt, m, vb;
623 |  |
624 | 4 | pkt = new Packet();
625 | 4 | pkt.community = options.community;
626 | 4 | 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.
632 | 4 | for (m = 0; m < 16 && c < options.oids.length; m++) {
633 | 30 | vb = new VarBind();
634 | 30 | vb.oid = options.oids[c];
635 | 30 | pkt.pdu.varbinds.push(vb);
636 | 30 | c++;
637 |  | }
638 |  |
639 | 4 | self.sendMsg(pkt, options, function (err, varbinds) {
640 | 4 | if (options.abortOnError && err) {
641 | 0 | callback(err);
642 |  | } else {
643 | 4 | if (varbinds) {
644 | 4 | results = results.concat(varbinds);
645 |  | }
646 | 4 | if (c < options.oids.length) {
647 | 1 | getOne(c);
648 |  | } else {
649 | 3 | callback(null, results);
650 |  | }
651 |  | }
652 |  | });
653 |  | }
654 |  |
655 | 3 | 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 |  |
661 | 1 | Session.prototype.getNext = function (options, callback) {
662 | 16 | var self = this, pkt;
663 |  |
664 | 16 | defaults(options, self.options);
665 | 16 | parseOids(options);
666 |  |
667 | 14 | if (!options.oid) {
668 | 1 | return callback(null, []);
669 |  | }
670 |  |
671 | 13 | pkt = new Packet();
672 | 13 | pkt.community = options.community;
673 | 13 | pkt.pdu.type = 1;
674 | 13 | pkt.pdu.varbinds[0].oid = options.oid;
675 | 13 | 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 |  |
683 | 1 | Session.prototype.getSubtree = function (options, callback) {
684 | 3 | var self = this, vbs = [];
685 |  |
686 | 3 | defaults(options, self.options);
687 | 3 | parseOids(options);
688 |  |
689 | 3 | if (!options.oid) {
690 | 1 | return callback(null, []);
691 |  | }
692 |  |
693 | 2 | 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) {
698 | 11 | var i;
699 | 11 | if (oid.length <= root.length) {
700 | 1 | return false;
701 |  | }
702 | 10 | for (i = 0; i < root.length; i++) {
703 | 79 | if (oid[i] !== root[i]) {
704 | 1 | return false;
705 |  | }
706 |  | }
707 | 9 | 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) {
716 | 11 | if (error) {
717 | 0 | callback(error);
718 |  | } else {
719 | 11 | if (inTree(options.startOid, varbinds[0].oid)) {
720 | 9 | if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') {
721 | 0 | callback(null, vbs);
722 |  | } else {
723 | 9 | vbs.push(varbinds[0]);
724 | 9 | var next = { oid: varbinds[0].oid };
725 | 9 | defaults(next, options);
726 | 9 | self.getNext(next, result);
727 |  | }
728 |  | } else {
729 | 2 | callback(null, vbs);
730 |  | }
731 |  | }
732 |  | }
733 |  |
734 | 2 | self.getNext(options, result);
735 |  | };
736 |  |
737 |  | // Close the socket. Necessary to finish the event loop and exit the program.
738 |  |
739 | 1 | Session.prototype.close = function () {
740 | 0 | this.socket.close();
741 |  | };
742 |  |