From a5c696788da1197eecbc03949a7d4225ccbadbbd Mon Sep 17 00:00:00 2001 From: Luong Quang Manh Date: Thu, 6 Jun 2019 11:10:18 +0700 Subject: [PATCH 1/3] Remove jshint and upgrade devDependencies --- .jshintrc | 15 --------------- package.json | 12 +++++------- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index f993d36..0000000 --- a/.jshintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "curly": true, - "eqeqeq": true, - "forin": true, - "immed": true, - "latedef": true, - "newcap": true, - "noarg": true, - "noempty": true, - "undef": true, - "trailing": true, - "node": true, - "nomen": false, - "plusplus": false -} diff --git a/package.json b/package.json index 29e82c8..dec7161 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,16 @@ "main": "lib/snmp.js", "scripts": { "test": "NODE_PATH=lib mocha -R spec", - "hint": "jshint *.js lib/*.js", "doc": "docco lib/* example.js 2>/dev/null", "cov": "jscoverage lib lib-cov && EXPRESS_COV=1 NODE_PATH=lib-cov mocha -R html-cov > docs/coverage.html" }, "dependencies": {}, "devDependencies": { - "docco": "~0.6.2", - "jscoverage": "~0.3.6", - "jshint": "~1.1.0", - "mocha": "~1.9.0", - "should": "~1.2.2", - "snmpjs": "~0.1.3" + "docco": "^0.8.0", + "jscoverage": "^0.6.0", + "mocha": "^6.1.4", + "should": "^13.2.3", + "snmpjs": "^0.1.3" }, "repository": { "type": "git", From 292a4f0e4f74116c3e98f19ed0ef92bbc1ccc653 Mon Sep 17 00:00:00 2001 From: Luong Quang Manh Date: Thu, 6 Jun 2019 11:18:45 +0700 Subject: [PATCH 2/3] Add ESLint for linting and Prettier for autoformatting --- .eslintrc.json | 15 +++++++++++++++ .prettierrc.json | 5 +++++ package.json | 3 +++ 3 files changed, 23 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .prettierrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9bce10c --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "env": { + "es6": true, + "mocha": true, + "node": true + }, + "extends": ["eslint:recommended"], + "parserOptions": { + "ecmaVersion": 2015, + "ecmaFeatures": { + "impliedStrict": true + } + }, + "rules": {} +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..7eca9a2 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "printWidth": 90, + "singleQuote": true, + "arrowParens": "always" +} diff --git a/package.json b/package.json index dec7161..90132d7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "license": "MIT", "main": "lib/snmp.js", "scripts": { + "lint": "eslint .", "test": "NODE_PATH=lib mocha -R spec", "doc": "docco lib/* example.js 2>/dev/null", "cov": "jscoverage lib lib-cov && EXPRESS_COV=1 NODE_PATH=lib-cov mocha -R html-cov > docs/coverage.html" @@ -21,8 +22,10 @@ "dependencies": {}, "devDependencies": { "docco": "^0.8.0", + "eslint": "^5.16.0", "jscoverage": "^0.6.0", "mocha": "^6.1.4", + "prettier": "^1.17.1", "should": "^13.2.3", "snmpjs": "^0.1.3" }, From e82dc751cf47455d597252eaf514e6dadff6d03f Mon Sep 17 00:00:00 2001 From: Luong Quang Manh Date: Thu, 6 Jun 2019 12:10:59 +0700 Subject: [PATCH 3/3] Fix linting errors and reformat codebase --- lib/asn1ber.js | 649 ++++++++++++++-------------- lib/snmp.js | 1127 ++++++++++++++++++++++++------------------------ 2 files changed, 904 insertions(+), 872 deletions(-) diff --git a/lib/asn1ber.js b/lib/asn1ber.js index 840d322..adb4e6f 100644 --- a/lib/asn1ber.js +++ b/lib/asn1ber.js @@ -4,56 +4,54 @@ // // (c) 2012 Jakob Borg, Nym Networks -"use strict"; - // We define constants for the commonly used ASN.1 types in SNMP. var T = { - Integer: 0x02, - OctetString: 0x04, - Null: 0x05, - ObjectIdentifier: 0x06, - Sequence: 0x30, - IpAddress: 0x40, - Counter: 0x41, - Gauge: 0x42, - TimeTicks: 0x43, - Opaque: 0x44, - NsapAddress: 0x45, - Counter64: 0x46, - NoSuchObject: 0x80, - NoSuchInstance: 0x81, - EndOfMibView: 0x82, - PDUBase: 0xA0 + Integer: 0x02, + OctetString: 0x04, + Null: 0x05, + ObjectIdentifier: 0x06, + Sequence: 0x30, + IpAddress: 0x40, + Counter: 0x41, + Gauge: 0x42, + TimeTicks: 0x43, + Opaque: 0x44, + NsapAddress: 0x45, + Counter64: 0x46, + NoSuchObject: 0x80, + NoSuchInstance: 0x81, + EndOfMibView: 0x82, + PDUBase: 0xa0 }; var P = { - GetRequestPDU: 0x00, - GetNextRequestPDU: 0x01, - GetResponsePDU: 0x02, - SetRequestPDU: 0x03 + GetRequestPDU: 0x00, + GetNextRequestPDU: 0x01, + GetResponsePDU: 0x02, + SetRequestPDU: 0x03 }; var E = { - NoError: 0, - TooBig: 1, - NoSuchName: 2, - BadValue: 3, - ReadOnly: 4, - GenErr: 5, - NoAccess: 6, - WrongType: 7, - WrongLength: 8, - WrongEncoding: 9, - WrongValue: 10, - NoCreation: 11, - InconsistentValue: 12, - ResourceUnavailable: 13, - CommitFailed: 14, - UndoFailed: 15, - AuthorizationError: 16, - NotWritable: 17, - InconsistentName: 18 + NoError: 0, + TooBig: 1, + NoSuchName: 2, + BadValue: 3, + ReadOnly: 4, + GenErr: 5, + NoAccess: 6, + WrongType: 7, + WrongLength: 8, + WrongEncoding: 9, + WrongValue: 10, + NoCreation: 11, + InconsistentValue: 12, + ResourceUnavailable: 13, + CommitFailed: 14, + UndoFailed: 15, + AuthorizationError: 16, + NotWritable: 17, + InconsistentName: 18 }; var LOG256 = Math.log(256); @@ -71,23 +69,23 @@ exports.unittest = {}; // Encode a length as it should be encoded. function lengthArray(len) { - var arr = []; - - if (len <= 127) { - // Return a single byte if the value is 127 or less. - return [ len ]; - } else { - // Otherwise encode it as a MSB base-256 integer. - while (len > 0) { - arr.push(len % 256); - len = parseInt(len / 256, 10); - } - // Add a length byte in front and set the high bit to indicate - // that this is a longer value than one byte. - arr.push(128 + arr.length); - arr.reverse(); - return arr; + var arr = []; + + if (len <= 127) { + // Return a single byte if the value is 127 or less. + return [len]; + } else { + // Otherwise encode it as a MSB base-256 integer. + while (len > 0) { + arr.push(len % 256); + len = parseInt(len / 256, 10); } + // Add a length byte in front and set the high bit to indicate + // that this is a longer value than one byte. + arr.push(128 + arr.length); + arr.reverse(); + return arr; + } } exports.unittest.lengthArray = lengthArray; @@ -96,19 +94,19 @@ exports.unittest.lengthArray = lengthArray; // This is used for Sequence and other constructed types. function wrapper(type, contents) { - var buf, len, i; - - // Get the encoded length of the contents - len = lengthArray(contents.length); - - // Set up a buffer with the type and length bytes plus a straight copy of the content. - buf = new Buffer(1 + contents.length + len.length); - buf[0] = type; - for (i = 1; i < len.length + 1; i++) { - buf[i] = len[i - 1]; - } - contents.copy(buf, len.length + 1, 0); - return buf; + var buf, len, i; + + // Get the encoded length of the contents + len = lengthArray(contents.length); + + // Set up a buffer with the type and length bytes plus a straight copy of the content. + buf = new Buffer(1 + contents.length + len.length); + buf[0] = type; + for (i = 1; i < len.length + 1; i++) { + buf[i] = len[i - 1]; + } + contents.copy(buf, len.length + 1, 0); + return buf; } // Get the encoded representation of a number in an OID. @@ -119,16 +117,16 @@ function wrapper(type, contents) { // two (.1.3) which are handled specially. function oidInt(val) { - var bytes = []; + var bytes = []; - bytes.push(val % 128); + bytes.push(val % 128); + val = parseInt(val / 128, 10); + while (val > 127) { + bytes.push(128 + (val % 128)); val = parseInt(val / 128, 10); - while (val > 127) { - bytes.push(128 + val % 128); - val = parseInt(val / 128, 10); - } - bytes.push(val + 128); - return bytes.reverse(); + } + bytes.push(val + 128); + return bytes.reverse(); } // Encode an OID. The first two number are encoded specially @@ -136,64 +134,66 @@ function oidInt(val) { // unless the number exceeds 127. If so, it's encoded as several base-127 // octets with the high bit set to indicate continuation. function oidArray(oid) { - var bytes, i, val; - - // Enforce some minimum requirements on the OID. - if (oid.length < 2) { - throw new Error("Minimum OID length is two."); - } else if (oid[0] > 2) { - throw new Error("Invalid OID"); - } else if (oid[0] == 0 && oid[1] > 39) { - throw new Error("Invalid OID"); - } else if (oid[0] == 1 && oid[1] > 39) { - throw new Error("Invalid OID"); - } else if (oid[0] == 2 && oid[1] > 79) { - throw new Error("Invalid OID"); - } - - // Calculate the first byte of the encoded OID according to the 'special' rule. - bytes = [ 40 * oid[0] + oid[1] ]; - - // For the rest of the OID, encode each number individually and add the - // resulting bytes to the buffer. - for (i = 2; i < oid.length; i++) { - val = oid[i]; - if (val > 127) { - bytes = bytes.concat(oidInt(val)); - } else { - bytes.push(val); - } + var bytes, i, val; + + // Enforce some minimum requirements on the OID. + if (oid.length < 2) { + throw new Error('Minimum OID length is two.'); + } else if (oid[0] > 2) { + throw new Error('Invalid OID'); + } else if (oid[0] == 0 && oid[1] > 39) { + throw new Error('Invalid OID'); + } else if (oid[0] == 1 && oid[1] > 39) { + throw new Error('Invalid OID'); + } else if (oid[0] == 2 && oid[1] > 79) { + throw new Error('Invalid OID'); + } + + // Calculate the first byte of the encoded OID according to the 'special' rule. + bytes = [40 * oid[0] + oid[1]]; + + // For the rest of the OID, encode each number individually and add the + // resulting bytes to the buffer. + for (i = 2; i < oid.length; i++) { + val = oid[i]; + if (val > 127) { + bytes = bytes.concat(oidInt(val)); + } else { + bytes.push(val); } + } - return bytes; + return bytes; } // Divide an integer into base-256 bytes. // Most significant byte first. function intArray(val) { - var array = [], encVal = val, bytes; - - if (val === 0) { - array.push(0); - } else { - if (val < 0) { - bytes = Math.floor(1 + Math.log(-val) / LOG256); - // Encode negatives as 32-bit two's complement. Let's hope that fits. - encVal += Math.pow(2, 8 * bytes); - } - while (encVal > 0) { - array.push(encVal % 256); - encVal = parseInt(encVal / 256, 10); - } + var array = [], + encVal = val, + bytes; + + if (val === 0) { + array.push(0); + } else { + if (val < 0) { + bytes = Math.floor(1 + Math.log(-val) / LOG256); + // Encode negatives as 32-bit two's complement. Let's hope that fits. + encVal += Math.pow(2, 8 * bytes); } - - // Do not produce integers that look negative (high bit - // of first byte set). - if (val > 0 && array[array.length - 1] >= 0x80) { - array.push(0); + while (encVal > 0) { + array.push(encVal % 256); + encVal = parseInt(encVal / 256, 10); } + } + + // Do not produce integers that look negative (high bit + // of first byte set). + if (val > 0 && array[array.length - 1] >= 0x80) { + array.push(0); + } - return array.reverse(); + return array.reverse(); } // Functions to encode ASN.1 from native objects @@ -205,158 +205,162 @@ function intArray(val) { // Javascript doesn't even **have** integers so some precision might get lost. function encodeIntegerish(val, type) { - var i, arr, buf; + var i, arr, buf; - // Get the bytes that we're going to encode. - arr = intArray(val); + // Get the bytes that we're going to encode. + arr = intArray(val); - // Now that we know the length, we allocate a buffer of the required size. - // We set the type and length bytes appropriately. - buf = new Buffer(2 + arr.length); - buf[0] = type; - buf[1] = arr.length; + // Now that we know the length, we allocate a buffer of the required size. + // We set the type and length bytes appropriately. + buf = new Buffer(2 + arr.length); + buf[0] = type; + buf[1] = arr.length; - // Copy the bytes into the array. - for (i = 0; i < arr.length; i++) { - buf[i + 2] = arr[i]; - } + // Copy the bytes into the array. + for (i = 0; i < arr.length; i++) { + buf[i + 2] = arr[i]; + } - return buf; + return buf; } // Integer type, 0x02 -exports.encodeInteger = function (val) { - return(encodeIntegerish(val, T.Integer)); +exports.encodeInteger = function(val) { + return encodeIntegerish(val, T.Integer); }; // Gauge type, 0x42 -exports.encodeGauge = function (val) { - return(encodeIntegerish(val, T.Gauge)); +exports.encodeGauge = function(val) { + return encodeIntegerish(val, T.Gauge); }; // Counter type, 0x41 -exports.encodeCounter = function (val) { - return(encodeIntegerish(val, T.Counter)); +exports.encodeCounter = function(val) { + return encodeIntegerish(val, T.Counter); }; // TimeTicks type, 0x43 -exports.encodeTimeTicks = function (val) { - return(encodeIntegerish(val, T.TimeTicks)); +exports.encodeTimeTicks = function(val) { + return encodeIntegerish(val, T.TimeTicks); }; // Create the representation of a Null, `05 00`. -exports.encodeNull = function () { - var buf = new Buffer(2); - buf[0] = T.Null; - buf[1] = 0; - return buf; +exports.encodeNull = function() { + var buf = new Buffer(2); + buf[0] = T.Null; + buf[1] = 0; + return buf; }; // NoSuchObject type, 0x80 exports.encodeNoSuchObject = function() { - var buf = new Buffer(2); - buf[0] = T.NoSuchObject; - buf[1] = 0; - return buf; + var buf = new Buffer(2); + buf[0] = T.NoSuchObject; + buf[1] = 0; + return buf; }; // NoSuchInstance type, 0x81 exports.encodeNoSuchInstance = function() { - var buf = new Buffer(2); - buf[0] = T.NoSuchInstance; - buf[1] = 0; - return buf; + var buf = new Buffer(2); + buf[0] = T.NoSuchInstance; + buf[1] = 0; + return buf; }; // EndOfMibView type, 0x82 exports.encodeEndOfMibView = function() { - var buf = new Buffer(2); - buf[0] = T.EndOfMibView; - buf[1] = 0; - return buf; + var buf = new Buffer(2); + buf[0] = T.EndOfMibView; + buf[1] = 0; + return buf; }; // Encode a Sequence, which is a wrapper of type `30`. -exports.encodeSequence = function (contents) { - return wrapper(T.Sequence, contents); +exports.encodeSequence = function(contents) { + return wrapper(T.Sequence, contents); }; // Encode an OctetString, which is a wrapper of type `04`. -exports.encodeOctetString = function (string) { - var buf, contents; +exports.encodeOctetString = function(string) { + var contents; - if (typeof string === 'string') { - contents = new Buffer(string); - } else if (Buffer.isBuffer(string)) { - contents = string; - } else { - throw new Error('Only Buffer and string types are acceptable as OctetString.'); - } + if (typeof string === 'string') { + contents = new Buffer(string); + } else if (Buffer.isBuffer(string)) { + contents = string; + } else { + throw new Error('Only Buffer and string types are acceptable as OctetString.'); + } - return wrapper(T.OctetString, contents); + return wrapper(T.OctetString, contents); }; // Encode an IpAddress, which is a wrapper of type `40`. -exports.encodeIpAddress = function (address) { - var contents, octets, value = []; - - if (typeof address !== 'string' && !Buffer.isBuffer(address)) { - throw new Error('Only Buffer and string types are acceptable as OctetString.'); +exports.encodeIpAddress = function(address) { + var contents, + octets, + value = []; + + if (typeof address !== 'string' && !Buffer.isBuffer(address)) { + throw new Error('Only Buffer and string types are acceptable as OctetString.'); + } + + // assume that the string is in dotted decimal format ipv4 + // also, use toString in case a buffer was passed in. + + octets = address.toString().split('.'); + if (octets.length !== 4) { + throw new Error('IP Addresses must be specified in dotted decimal format.'); + } + octets.forEach(function(octet) { + var octetValue = parseInt(octet, 10); + if (octet < 0 || octet > 255) { + throw new Error( + 'IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets) + ); } + value.push(octetValue); + }); - // assume that the string is in dotted decimal format ipv4 - // also, use toString in case a buffer was passed in. + contents = new Buffer(value); - octets = address.toString().split('.'); - if (octets.length !== 4) { - throw new Error('IP Addresses must be specified in dotted decimal format.'); - } - octets.forEach(function (octet) { - var octetValue = parseInt(octet, 10); - if (octet < 0 || octet > 255) { - throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets)); - } - value.push(octetValue); - }); - - contents = new Buffer(value); - - return wrapper(T.IpAddress, contents); + return wrapper(T.IpAddress, contents); }; // Encode an ObjectId. -exports.encodeOid = function (oid) { - var buf, bytes, i, len; +exports.encodeOid = function(oid) { + var buf, bytes, i, len; - // Get the encoded format of the OID. - bytes = oidArray(oid); + // Get the encoded format of the OID. + bytes = oidArray(oid); - // Get the encoded format of the length - len = lengthArray(bytes.length); + // Get the encoded format of the length + len = lengthArray(bytes.length); - // Fill in the buffer with type, length and OID data. - buf = new Buffer(1 + bytes.length + len.length); - buf[0] = T.ObjectIdentifier; - for (i = 1; i < len.length + 1; i++) { - buf[i] = len[i - 1]; - } - for (i = len.length + 1; i < bytes.length + len.length + 1; i++) { - buf[i] = bytes[i - len.length - 1]; - } + // Fill in the buffer with type, length and OID data. + buf = new Buffer(1 + bytes.length + len.length); + buf[0] = T.ObjectIdentifier; + for (i = 1; i < len.length + 1; i++) { + buf[i] = len[i - 1]; + } + for (i = len.length + 1; i < bytes.length + len.length + 1; i++) { + buf[i] = bytes[i - len.length - 1]; + } - return buf; + return buf; }; // Encode an SNMP request with specified `contents`. // The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`. -exports.encodeRequest = function (type, contents) { - return wrapper(T.PDUBase + type, contents); +exports.encodeRequest = function(type, contents) { + return wrapper(T.PDUBase + type, contents); }; // Functions to parse ASN.1 to native objects @@ -364,23 +368,23 @@ exports.encodeRequest = function (type, contents) { // Parse and return type, data length and header length. function typeAndLength(buf) { - var res, len, i; - - res = { type: buf[0], len: 0, header: 1 }; - if (buf[1] < 128) { - // If bit 8 is zero, this byte indicates the content length (up to 127 bytes). - res.len = buf[1]; - res.header += 1; - } else { - // If bit 8 is 1, bits 0 to 7 indicate the number of following legth bytes. - // These bytes are a simple msb base-256 integer indicating the content length. - for (i = 0; i < buf[1] - 128; i++) { - res.len *= 256; - res.len += buf[i + 2]; - } - res.header += buf[1] - 128 + 1; + var res, i; + + res = { type: buf[0], len: 0, header: 1 }; + if (buf[1] < 128) { + // If bit 8 is zero, this byte indicates the content length (up to 127 bytes). + res.len = buf[1]; + res.header += 1; + } else { + // If bit 8 is 1, bits 0 to 7 indicate the number of following legth bytes. + // These bytes are a simple msb base-256 integer indicating the content length. + for (i = 0; i < buf[1] - 128; i++) { + res.len *= 256; + res.len += buf[i + 2]; } - return res; + res.header += buf[1] - 128 + 1; + } + return res; } exports.typeAndLength = typeAndLength; @@ -388,124 +392,129 @@ exports.typeAndLength = typeAndLength; // Parse a buffer containing a representation of an integer. // Verifies the type, then multiplies in each byte as it comes. -exports.parseInteger = function (buf) { - var i, val, type, len; - - type = buf[0]; - len = buf[1]; - - if (type !== T.Integer && type !== T.Counter && - type !== T.Counter64 && type !== T.Gauge && - type !== T.TimeTicks) { - throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer'); - } - - val = 0; - for (i = 0; i < len; i++) { - val *= 256; - val += buf[i + 2]; - } - - if (buf[2] > 127 && type === T.Integer) { - return val - Math.pow(2, 8 * buf[1]); - } else { - return val; - } +exports.parseInteger = function(buf) { + var i, val, type, len; + + type = buf[0]; + len = buf[1]; + + if ( + type !== T.Integer && + type !== T.Counter && + type !== T.Counter64 && + type !== T.Gauge && + type !== T.TimeTicks + ) { + throw new Error( + 'Buffer ' + buf.toString('hex') + ' does not appear to be an Integer' + ); + } + + val = 0; + for (i = 0; i < len; i++) { + val *= 256; + val += buf[i + 2]; + } + + if (buf[2] > 127 && type === T.Integer) { + return val - Math.pow(2, 8 * buf[1]); + } else { + return val; + } }; // Parse a buffer containing a representation of an OctetString. // Verify the type, then just grab the string out of the buffer. -exports.parseOctetString = function (buf) { - var i, len, lenBytes = 0; - - if (buf[0] !== T.OctetString) { - throw new Error('Buffer does not appear to be an OctetString'); - } - - // SNMP doesn't specify an encoding so I pick UTF-8 as the 'most standard' - // encoding. We'll see if that assumption survives contact with actual reality. - - len = buf[1]; - if (len > 128) { - // Multi byte length encoding - lenBytes = len - 128; - len = 0; - for (i = 0; i < lenBytes; i++) { - len *= 256; - len += buf[2+i]; - } +exports.parseOctetString = function(buf) { + var i, + len, + lenBytes = 0; + + if (buf[0] !== T.OctetString) { + throw new Error('Buffer does not appear to be an OctetString'); + } + + // SNMP doesn't specify an encoding so I pick UTF-8 as the 'most standard' + // encoding. We'll see if that assumption survives contact with actual reality. + + len = buf[1]; + if (len > 128) { + // Multi byte length encoding + lenBytes = len - 128; + len = 0; + for (i = 0; i < lenBytes; i++) { + len *= 256; + len += buf[2 + i]; } - return buf.toString('utf-8', 2 + lenBytes, 2 + lenBytes + len); + } + return buf.toString('utf-8', 2 + lenBytes, 2 + lenBytes + len); }; // Parse a buffer containing a representation of an ObjectIdentifier. // Verify the type, then apply the relevent encoding rules. -exports.parseOid = function (buf) { - var oid, val, i, o1, o2; +exports.parseOid = function(buf) { + var oid, val, i, o1, o2; - if (buf[0] !== T.ObjectIdentifier) { - throw new Error('Buffer does not appear to be an ObjectIdentifier'); - } + if (buf[0] !== T.ObjectIdentifier) { + throw new Error('Buffer does not appear to be an ObjectIdentifier'); + } - // The first byte contains the first two numbers in the OID. They're - // magical! They're compactly encoded in a special way! KILL ME NOW! - o1 = parseInt(buf[2] / 40, 10); - if (o1 > 2) { - o1 = 2; - } - o2 = buf[2] - 40 * o1; - oid = [o1, o2]; - - // The rest of the data is a base-128-encoded OID - for (i = 0; i < buf[1] - 1; i++) { - val = 0; - while (buf[i + 3] >= 128) { - val += buf[i + 3] - 128; - val *= 128; - i++; - } - val += buf[i + 3]; - oid.push(val); + // The first byte contains the first two numbers in the OID. They're + // magical! They're compactly encoded in a special way! KILL ME NOW! + o1 = parseInt(buf[2] / 40, 10); + if (o1 > 2) { + o1 = 2; + } + o2 = buf[2] - 40 * o1; + oid = [o1, o2]; + + // The rest of the data is a base-128-encoded OID + for (i = 0; i < buf[1] - 1; i++) { + val = 0; + while (buf[i + 3] >= 128) { + val += buf[i + 3] - 128; + val *= 128; + i++; } + val += buf[i + 3]; + oid.push(val); + } - return oid; + return oid; }; // Parse a buffer containing a representation of an array type. // This is for example an IpAddress. -exports.parseArray = function (buf) { - var i, nelem, array; +exports.parseArray = function(buf) { + var i, array; - if (buf[0] !== T.IpAddress) { - throw new Error('Buffer does not appear to be an array type.'); - } + if (buf[0] !== T.IpAddress) { + throw new Error('Buffer does not appear to be an array type.'); + } - nelem = buf[1]; - array = []; + array = []; - for (i = 0; i < buf[1]; i++) { - array.push(buf[i + 2]); - } + for (i = 0; i < buf[1]; i++) { + array.push(buf[i + 2]); + } - return array; + return array; }; // Parse a buffer containing a representation of an opaque type. // This is for example an IpAddress. -exports.parseOpaque = function (buf) { - var hdr; +exports.parseOpaque = function(buf) { + var hdr; - hdr = typeAndLength(buf); + hdr = typeAndLength(buf); - if (hdr.type !== T.Opaque) { - throw new Error('Buffer does not appear to be an opaque type.'); - } + if (hdr.type !== T.Opaque) { + throw new Error('Buffer does not appear to be an opaque type.'); + } - return '0x' + buf.slice(hdr.header).toString('hex'); + return '0x' + buf.slice(hdr.header).toString('hex'); }; - -/*globals exports: false*/ diff --git a/lib/snmp.js b/lib/snmp.js index 541ae32..76408f6 100644 --- a/lib/snmp.js +++ b/lib/snmp.js @@ -6,8 +6,6 @@ // // (c) 2012 Jakob Borg, Nym Networks -"use strict"; - // Code // ----- // This file implements a structure representing an SNMP message @@ -26,8 +24,8 @@ exports.DataTypes = asn1ber.types; exports.Errors = asn1ber.errors; var versions = { - SNMPv1: 0, - SNMPv2c: 1 + SNMPv1: 0, + SNMPv2c: 1 }; exports.Versions = versions; @@ -36,24 +34,24 @@ exports.Versions = versions; // A `VarBind` is the innermost structure, containing an OID-Value pair. function VarBind() { - this.type = 5; - this.value = null; + this.type = 5; + this.value = null; } // The `PDU` contains the SNMP request or response fields and a list of `VarBinds`. function PDU() { - this.type = asn1ber.pduTypes.GetRequestPDU; - this.reqid = 1; - this.error = 0; - this.errorIndex = 0; - this.varbinds = [ new VarBind() ]; + this.type = asn1ber.pduTypes.GetRequestPDU; + this.reqid = 1; + this.error = 0; + this.errorIndex = 0; + this.varbinds = [new VarBind()]; } // The `Packet` contains the SNMP version and community and the `PDU`. function Packet() { - this.version = versions.SNMPv2c; - this.community = 'public'; - this.pdu = new PDU(); + this.version = versions.SNMPv2c; + this.community = 'public'; + this.pdu = new PDU(); } // Allow consumers to create packet structures from scratch. @@ -64,82 +62,82 @@ exports.Packet = Packet; // Concatenate several buffers to one. function concatBuffers(buffers) { - var total, cur = 0, buf; - - // First we calculate the total length, - total = buffers.reduce(function (tot, b) { - return tot + b.length; - }, 0); - - // then we allocate a new Buffer large enough to contain all data, - buf = new Buffer(total); - buffers.forEach(function (buffer) { - // finally we copy the data into the new larger buffer. - buffer.copy(buf, cur, 0); - cur += buffer.length; - }); - - return buf; + var total, + cur = 0, + buf; + + // First we calculate the total length, + total = buffers.reduce(function(tot, b) { + return tot + b.length; + }, 0); + + // then we allocate a new Buffer large enough to contain all data, + buf = new Buffer(total); + buffers.forEach(function(buffer) { + // finally we copy the data into the new larger buffer. + buffer.copy(buf, cur, 0); + cur += buffer.length; + }); + + return buf; } // Clear a pending packet when it times out or is successfully received. function clearRequest(reqs, reqid) { - var self = this; - - var entry = reqs[reqid]; - if (entry) { - if (entry.timeout) { - clearTimeout(entry.timeout); - } - delete reqs[reqid]; + var entry = reqs[reqid]; + if (entry) { + if (entry.timeout) { + clearTimeout(entry.timeout); } + delete reqs[reqid]; + } } // Convert a string formatted OID to an array, leaving anything non-string alone. function parseSingleOid(oid) { - if (typeof oid !== 'string') { - return oid; - } - - if (oid[0] !== '.') { - throw new Error('Invalid OID format'); - } - - oid = oid.split('.') - .filter(function (s) { - return s.length > 0; - }) - .map(function (s) { - return parseInt(s, 10); - }); - + if (typeof oid !== 'string') { return oid; + } + + if (oid[0] !== '.') { + throw new Error('Invalid OID format'); + } + + oid = oid + .split('.') + .filter(function(s) { + return s.length > 0; + }) + .map(function(s) { + return parseInt(s, 10); + }); + + return oid; } // Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings. function parseOids(options) { - if (options.oid) { - options.oid = parseSingleOid(options.oid); - } - if (options.oids) { - options.oids = options.oids.map(parseSingleOid); - } + if (options.oid) { + options.oid = parseSingleOid(options.oid); + } + if (options.oids) { + options.oids = options.oids.map(parseSingleOid); + } } -// Update targ with attributes from _defs. // Any existing attributes on targ are untouched. -function defaults(targ, _defs) { - [].slice.call(arguments, 1).forEach(function (def) { - Object.keys(def).forEach(function (key) { - if (!targ.hasOwnProperty(key)) { - targ[key] = def[key]; - } - }); +function defaults(targ) { + [].slice.call(arguments, 1).forEach(function(def) { + Object.keys(def).forEach(function(key) { + if (!targ.hasOwnProperty(key)) { + targ[key] = def[key]; + } }); + }); } // Encode structure to ASN.1 BER @@ -148,65 +146,66 @@ function defaults(targ, _defs) { // Return an ASN.1 BER encoding of a Packet structure. // This is suitable for transmission on a UDP socket. function encode(pkt) { - var version, community, reqid, err, erridx, vbs, pdu, message; - - // We only support SNMPv1 and SNMPv2c, so enforce those version stamps. - if (pkt.version !== versions.SNMPv1 && pkt.version !== versions.SNMPv2c) { - throw new Error('Only SNMPv1 and SNMPv2c are supported.'); + var version, community, reqid, err, erridx, vbs, pdu, message; + + // We only support SNMPv1 and SNMPv2c, so enforce those version stamps. + if (pkt.version !== versions.SNMPv1 && pkt.version !== versions.SNMPv2c) { + throw new Error('Only SNMPv1 and SNMPv2c are supported.'); + } + + // Encode the message header fields. + version = asn1ber.encodeInteger(pkt.version); + community = asn1ber.encodeOctetString(pkt.community); + + // Encode the PDU header fields. + reqid = asn1ber.encodeInteger(pkt.pdu.reqid); + err = asn1ber.encodeInteger(pkt.pdu.error); + erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex); + + // Encode the PDU varbinds. + vbs = []; + pkt.pdu.varbinds.forEach(function(vb) { + var oid = asn1ber.encodeOid(vb.oid), + val; + + if (vb.type === asn1ber.types.Null || vb.value === null) { + val = asn1ber.encodeNull(); + } else if (vb.type === asn1ber.types.Integer) { + val = asn1ber.encodeInteger(vb.value); + } else if (vb.type === asn1ber.types.Gauge) { + val = asn1ber.encodeGauge(vb.value); + } else if (vb.type === asn1ber.types.IpAddress) { + val = asn1ber.encodeIpAddress(vb.value); + } else if (vb.type === asn1ber.types.OctetString) { + val = asn1ber.encodeOctetString(vb.value); + } else if (vb.type === asn1ber.types.ObjectIdentifier) { + val = asn1ber.encodeOid(vb.value, true); + } else if (vb.type === asn1ber.types.Counter) { + val = asn1ber.encodeCounter(vb.value); + } else if (vb.type === asn1ber.types.TimeTicks) { + val = asn1ber.encodeTimeTicks(vb.value); + } else if (vb.type === asn1ber.types.NoSuchObject) { + val = asn1ber.encodeNoSuchObject(); + } else if (vb.type === asn1ber.types.NoSuchInstance) { + val = asn1ber.encodeNoSuchInstance(); + } else if (vb.type === asn1ber.types.EndOfMibView) { + val = asn1ber.encodeEndOfMibView(); + } else { + throw new Error('Unknown varbind type "' + vb.type + '" in encoding.'); } + vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val]))); + }); - // Encode the message header fields. - version = asn1ber.encodeInteger(pkt.version); - community = asn1ber.encodeOctetString(pkt.community); - - // Encode the PDU header fields. - reqid = asn1ber.encodeInteger(pkt.pdu.reqid); - err = asn1ber.encodeInteger(pkt.pdu.error); - erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex); - - // Encode the PDU varbinds. - vbs = []; - pkt.pdu.varbinds.forEach(function (vb) { - var oid = asn1ber.encodeOid(vb.oid), val; - - if (vb.type === asn1ber.types.Null || vb.value === null) { - val = asn1ber.encodeNull(); - } else if (vb.type === asn1ber.types.Integer) { - val = asn1ber.encodeInteger(vb.value); - } else if (vb.type === asn1ber.types.Gauge) { - val = asn1ber.encodeGauge(vb.value); - } else if (vb.type === asn1ber.types.IpAddress) { - val = asn1ber.encodeIpAddress(vb.value); - } else if (vb.type === asn1ber.types.OctetString) { - val = asn1ber.encodeOctetString(vb.value); - } else if (vb.type === asn1ber.types.ObjectIdentifier) { - val = asn1ber.encodeOid(vb.value, true); - } else if (vb.type === asn1ber.types.Counter) { - val = asn1ber.encodeCounter(vb.value); - } else if (vb.type === asn1ber.types.TimeTicks) { - val = asn1ber.encodeTimeTicks(vb.value); - } else if (vb.type === asn1ber.types.NoSuchObject) { - val = asn1ber.encodeNoSuchObject(); - } else if (vb.type === asn1ber.types.NoSuchInstance) { - val = asn1ber.encodeNoSuchInstance(); - } else if (vb.type === asn1ber.types.EndOfMibView) { - val = asn1ber.encodeEndOfMibView(); - } else { - throw new Error('Unknown varbind type "' + vb.type + '" in encoding.'); - } - vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val]))); - }); - - // Concatenate all the varbinds together. - vbs = asn1ber.encodeSequence(concatBuffers(vbs)); + // Concatenate all the varbinds together. + vbs = asn1ber.encodeSequence(concatBuffers(vbs)); - // Create the PDU by concatenating the inner fields and adding a request structure around it. - pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs])); + // Create the PDU by concatenating the inner fields and adding a request structure around it. + pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs])); - // Create the message by concatenating the header fields and the PDU. - message = asn1ber.encodeSequence(concatBuffers([version, community, pdu])); + // Create the message by concatenating the header fields and the PDU. + message = asn1ber.encodeSequence(concatBuffers([version, community, pdu])); - return message; + return message; } exports.encode = encode; @@ -219,130 +218,132 @@ exports.encode = encode; // make us blow up. function parse(buf) { - var pkt, oid, bvb, vb, hdr, vbhdr; + var pkt, bvb, vb, hdr; - pkt = new Packet(); + pkt = new Packet(); - // First we have a sequence marker (two bytes). - // We don't care about those, so cut them off. - hdr = asn1ber.typeAndLength(buf); - assert.equal(asn1ber.types.Sequence, hdr.type); - buf = buf.slice(hdr.header); + // First we have a sequence marker (two bytes). + // We don't care about those, so cut them off. + hdr = asn1ber.typeAndLength(buf); + assert.equal(asn1ber.types.Sequence, hdr.type); + buf = buf.slice(hdr.header); - // Then comes the version field (integer). Parse it and slice it. - pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); - buf = buf.slice(2 + buf[1]); + // Then comes the version field (integer). Parse it and slice it. + pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); + buf = buf.slice(2 + buf[1]); - // We then get the community. Parse and slice. - pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2)); - buf = buf.slice(2 + buf[1]); + // We then get the community. Parse and slice. + pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2)); + buf = buf.slice(2 + buf[1]); - // Here's the PDU structure. We're interested in the type. Slice the rest. - hdr = asn1ber.typeAndLength(buf); - assert.ok(hdr.type >= 0xA0); - pkt.pdu.type = hdr.type - 0xA0; - buf = buf.slice(hdr.header); + // Here's the PDU structure. We're interested in the type. Slice the rest. + hdr = asn1ber.typeAndLength(buf); + assert.ok(hdr.type >= 0xa0); + pkt.pdu.type = hdr.type - 0xa0; + buf = buf.slice(hdr.header); + + // The request id field. + pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); + buf = buf.slice(2 + buf[1]); + + // The error field. + pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); + buf = buf.slice(2 + buf[1]); - // The request id field. - pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); - buf = buf.slice(2 + buf[1]); + // The error index field. + pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); + buf = buf.slice(2 + buf[1]); - // The error field. - pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); - buf = buf.slice(2 + buf[1]); + // Here's the varbind list. Not interested. + hdr = asn1ber.typeAndLength(buf); + assert.equal(asn1ber.types.Sequence, hdr.type); + buf = buf.slice(hdr.header); - // The error index field. - pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); - buf = buf.slice(2 + buf[1]); + // Now comes the varbinds. There might be many, so we loop for as long as we have data. + pkt.pdu.varbinds = []; + while (buf[0] === asn1ber.types.Sequence) { + vb = new VarBind(); - // Here's the varbind list. Not interested. + // Slice off the sequence header. hdr = asn1ber.typeAndLength(buf); assert.equal(asn1ber.types.Sequence, hdr.type); - buf = buf.slice(hdr.header); - - // Now comes the varbinds. There might be many, so we loop for as long as we have data. - pkt.pdu.varbinds = []; - while (buf[0] === asn1ber.types.Sequence) { - vb = new VarBind(); - - // Slice off the sequence header. - hdr = asn1ber.typeAndLength(buf); - assert.equal(asn1ber.types.Sequence, hdr.type); - bvb = buf.slice(hdr.header, hdr.len + hdr.header); - - // Parse and save the ObjectIdentifier. - vb.oid = asn1ber.parseOid(bvb); - - // Parse the value. We use the type marker to figure out - // what kind of value it is and call the appropriate parser - // routine. For the SNMPv2c error types, we simply set the - // value to a text representation of the error and leave handling - // up to the user. - var vb_name_hdr = asn1ber.typeAndLength(bvb); - bvb = bvb.slice(vb_name_hdr.header + vb_name_hdr.len); - var vb_value_hdr = asn1ber.typeAndLength(bvb); - vb.type = vb_value_hdr.type; - if (vb.type === asn1ber.types.Null) { - // Null type. - vb.value = null; - } else if (vb.type === asn1ber.types.OctetString) { - // Octet string type. - vb.value = asn1ber.parseOctetString(bvb); - } else if (vb.type === asn1ber.types.Integer || - vb.type === asn1ber.types.Counter || - vb.type === asn1ber.types.Counter64 || - vb.type === asn1ber.types.TimeTicks || - vb.type === asn1ber.types.Gauge) { - // Integer type and it's derivatives that behave in the same manner. - vb.value = asn1ber.parseInteger(bvb); - } else if (vb.type === asn1ber.types.ObjectIdentifier) { - // Object identifier type. - vb.value = asn1ber.parseOid(bvb); - } else if (vb.type === asn1ber.types.IpAddress) { - // IP Address type. - vb.value = asn1ber.parseArray(bvb); - } else if (vb.type === asn1ber.types.Opaque) { - // Opaque type. The 'parsing' here is very light; basically we return a - // string representation of the raw bytes in hex. - vb.value = asn1ber.parseOpaque(bvb); - } else if (vb.type === asn1ber.types.EndOfMibView) { - // End of MIB view error, returned when attempting to GetNext beyond the end - // of the current view. - vb.value = 'endOfMibView'; - } else if (vb.type === asn1ber.types.NoSuchObject) { - // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist. - vb.value = 'noSuchObject'; - } else if (vb.type === asn1ber.types.NoSuchInstance) { - // No such instance error, returned when attempting to Get/GetNext an instance - // that doesn't exist in a given table. - vb.value = 'noSuchInstance'; - } else { - // Something else that we can't handle, so throw an error. - // The error will be caught and presented in a useful manner on stderr, - // with a dump of the message causing it. - throw new Error('Unrecognized value type ' + vb.type); - } + bvb = buf.slice(hdr.header, hdr.len + hdr.header); + + // Parse and save the ObjectIdentifier. + vb.oid = asn1ber.parseOid(bvb); + + // Parse the value. We use the type marker to figure out + // what kind of value it is and call the appropriate parser + // routine. For the SNMPv2c error types, we simply set the + // value to a text representation of the error and leave handling + // up to the user. + var vb_name_hdr = asn1ber.typeAndLength(bvb); + bvb = bvb.slice(vb_name_hdr.header + vb_name_hdr.len); + var vb_value_hdr = asn1ber.typeAndLength(bvb); + vb.type = vb_value_hdr.type; + if (vb.type === asn1ber.types.Null) { + // Null type. + vb.value = null; + } else if (vb.type === asn1ber.types.OctetString) { + // Octet string type. + vb.value = asn1ber.parseOctetString(bvb); + } else if ( + vb.type === asn1ber.types.Integer || + vb.type === asn1ber.types.Counter || + vb.type === asn1ber.types.Counter64 || + vb.type === asn1ber.types.TimeTicks || + vb.type === asn1ber.types.Gauge + ) { + // Integer type and it's derivatives that behave in the same manner. + vb.value = asn1ber.parseInteger(bvb); + } else if (vb.type === asn1ber.types.ObjectIdentifier) { + // Object identifier type. + vb.value = asn1ber.parseOid(bvb); + } else if (vb.type === asn1ber.types.IpAddress) { + // IP Address type. + vb.value = asn1ber.parseArray(bvb); + } else if (vb.type === asn1ber.types.Opaque) { + // Opaque type. The 'parsing' here is very light; basically we return a + // string representation of the raw bytes in hex. + vb.value = asn1ber.parseOpaque(bvb); + } else if (vb.type === asn1ber.types.EndOfMibView) { + // End of MIB view error, returned when attempting to GetNext beyond the end + // of the current view. + vb.value = 'endOfMibView'; + } else if (vb.type === asn1ber.types.NoSuchObject) { + // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist. + vb.value = 'noSuchObject'; + } else if (vb.type === asn1ber.types.NoSuchInstance) { + // No such instance error, returned when attempting to Get/GetNext an instance + // that doesn't exist in a given table. + vb.value = 'noSuchInstance'; + } else { + // Something else that we can't handle, so throw an error. + // The error will be caught and presented in a useful manner on stderr, + // with a dump of the message causing it. + throw new Error('Unrecognized value type ' + vb.type); + } - // Take the raw octet string value and preseve it as a buffer and hex string. - vb.valueRaw = bvb.slice(vb_value_hdr.header, vb_value_hdr.header + vb_value_hdr.len); - vb.valueHex = vb.valueRaw.toString('hex'); + // Take the raw octet string value and preseve it as a buffer and hex string. + vb.valueRaw = bvb.slice(vb_value_hdr.header, vb_value_hdr.header + vb_value_hdr.len); + vb.valueHex = vb.valueRaw.toString('hex'); - // Add the request id to the varbind (even though it doesn't really belong) - // so that it will be availble to the end user. - vb.requestId = pkt.pdu.reqid; + // Add the request id to the varbind (even though it doesn't really belong) + // so that it will be availble to the end user. + vb.requestId = pkt.pdu.reqid; - // Push whatever we parsed to the varbind list. - pkt.pdu.varbinds.push(vb); + // Push whatever we parsed to the varbind list. + pkt.pdu.varbinds.push(vb); - // Go fetch the next varbind, if there seems to be any. - if (buf.length > hdr.header + hdr.len) { - buf = buf.slice(hdr.header + hdr.len); - } else { - break; - } + // Go fetch the next varbind, if there seems to be any. + if (buf.length > hdr.header + hdr.len) { + buf = buf.slice(hdr.header + hdr.len); + } else { + break; } + } - return pkt; + return pkt; } exports.parse = parse; @@ -353,38 +354,38 @@ exports.parse = parse; // Compare two OIDs, returning -1, 0 or +1 depending on the relation between // oidA and oidB. -function compareOids (oidA, oidB) { - var mlen, i; - - // The undefined OID, if there is any, is deemed lesser. - if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') { - return 1; - } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') { - return -1; - } - - // Check each number part of the OIDs individually, and if there is any - // position where one OID is larger than the other, return accordingly. - // This will only check up to the minimum length of both OIDs. - mlen = Math.min(oidA.length, oidB.length); - for (i = 0; i < mlen; i++) { - if (oidA[i] > oidB[i]) { - return -1; - } else if (oidB[i] > oidA[i]) { - return 1; - } - } - - // If there is one OID that is longer than the other after the above comparison, - // consider the shorter OID to be lesser. - if (oidA.length > oidB.length) { - return -1; - } else if (oidB.length > oidA.length) { - return 1; - } else { - // The OIDs are obviously equal. - return 0; +function compareOids(oidA, oidB) { + var mlen, i; + + // The undefined OID, if there is any, is deemed lesser. + if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') { + return 1; + } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') { + return -1; + } + + // Check each number part of the OIDs individually, and if there is any + // position where one OID is larger than the other, return accordingly. + // This will only check up to the minimum length of both OIDs. + mlen = Math.min(oidA.length, oidB.length); + for (i = 0; i < mlen; i++) { + if (oidA[i] > oidB[i]) { + return -1; + } else if (oidB[i] > oidA[i]) { + return 1; } + } + + // If there is one OID that is longer than the other after the above comparison, + // consider the shorter OID to be lesser. + if (oidA.length > oidB.length) { + return -1; + } else if (oidB.length > oidA.length) { + return 1; + } else { + // The OIDs are obviously equal. + return 0; + } } exports.compareOids = compareOids; @@ -394,92 +395,96 @@ exports.compareOids = compareOids; // This is called for when we receive a message. -function msgReceived(msg, rinfo) { - var self = this, now = Date.now(), pkt, entry; +function msgReceived(msg) { + var self = this, + now = Date.now(), + pkt, + entry; + + if (msg.length === 0) { + // Not sure why we sometimes receive an empty message. + // As far as I'm concerned it shouldn't happen, but we'll ignore it + // and if it's necessary a retransmission of the request will be + // made later. + return; + } + + // Parse the packet, or call the informative + // parse error display if we fail. + try { + pkt = parse(msg); + } catch (error) { + return self.emit('error', error); + } + + // If this message's request id matches one we've sent, + // cancel any outstanding timeout and call the registered + // callback. + entry = self.reqs[pkt.pdu.reqid]; + if (entry) { + clearRequest(self.reqs, pkt.pdu.reqid); + + if (typeof entry.callback === 'function') { + if (pkt.pdu.error !== 0) { + // An error response should be reported as an error to the callback. + // We try to find the error description, or in worst case call it + // just "Unknown Error ". + var errorDescr = + Object.keys(asn1ber.errors).filter(function(key) { + return asn1ber.errors[key] === pkt.pdu.error; + })[0] || 'Unknown Error ' + pkt.pdu.error; + return entry.callback(new Error(errorDescr)); + } - if (msg.length === 0) { - // Not sure why we sometimes receive an empty message. - // As far as I'm concerned it shouldn't happen, but we'll ignore it - // and if it's necessary a retransmission of the request will be - // made later. - return; - } + pkt.pdu.varbinds.forEach(function(vb) { + vb.receiveStamp = now; + vb.sendStamp = entry.sendStamp; + }); - // Parse the packet, or call the informative - // parse error display if we fail. - try { - pkt = parse(msg); - } catch (error) { - return self.emit('error', error); - } - - // If this message's request id matches one we've sent, - // cancel any outstanding timeout and call the registered - // callback. - entry = self.reqs[pkt.pdu.reqid]; - if (entry) { - clearRequest(self.reqs, pkt.pdu.reqid); - - if (typeof entry.callback === 'function') { - if (pkt.pdu.error !== 0) { - // An error response should be reported as an error to the callback. - // We try to find the error description, or in worst case call it - // just "Unknown Error ". - var errorDescr = Object.keys(asn1ber.errors).filter(function (key) { - return asn1ber.errors[key] === pkt.pdu.error; - })[0] || 'Unknown Error ' + pkt.pdu.error; - return entry.callback(new Error(errorDescr)); - } - - pkt.pdu.varbinds.forEach(function (vb) { - vb.receiveStamp = now; - vb.sendStamp = entry.sendStamp; - }); - - entry.callback(null, pkt.pdu.varbinds); - } + entry.callback(null, pkt.pdu.varbinds); } + } } // Default options for new sessions and operations. exports.defaultOptions = { - host: 'localhost', - port: 161, - bindPort: 0, - community: 'public', - family: 'udp4', - timeouts: [ 5000, 5000, 5000, 5000 ], - version: versions.SNMPv2c + host: 'localhost', + port: 161, + bindPort: 0, + community: 'public', + family: 'udp4', + timeouts: [5000, 5000, 5000, 5000], + version: versions.SNMPv2c }; // This creates a new SNMP session. function Session(options) { - var self = this; - - self.options = options || {}; - defaults(self.options, exports.defaultOptions); - - self.reqs = {}; - self.socket = dgram.createSocket(self.options.family); - self.socket.on('message', self.options.msgReceived || msgReceived.bind(self)); - self.socket.on('close', function () { - // Remove the socket so we don't try to send a message on - // it when it's closed. - self.socket = undefined; - }); - self.socket.on('error', function () { - // Errors will be emitted here as well as on the callback to the send function. - // We handle them there, so doing anything here is unnecessary. - // But having no error handler trips up the test suite. - }); - // If exclusive is false (default), then cluster workers will use the same underlying handle, - // allowing connection handling duties to be shared. - // When exclusive is true, the handle is not shared, and attempted port sharing results in an error. - self.socket.bind({ - port: self.options.bindPort, // unless otherwise specified, get a random port automatically - exclusive: true // you should not share the same port, otherwise yours packages will be screwed up between workers - }); + var self = this; + + self.options = options || {}; + defaults(self.options, exports.defaultOptions); + + self.reqs = {}; + self.socket = dgram.createSocket(self.options.family); + self.socket.on('message', self.options.msgReceived || msgReceived.bind(self)); + self.socket.on('close', function() { + // Remove the socket so we don't try to send a message on + // it when it's closed. + self.socket = undefined; + }); + self.socket.on('error', function() { + // Errors will be emitted here as well as on the callback to the send function. + // We handle them there, so doing anything here is unnecessary. + // But having no error handler trips up the test suite. + }); + // If exclusive is false (default), then cluster workers will use the same underlying handle, + // allowing connection handling duties to be shared. + // When exclusive is true, the handle is not shared, and attempted port sharing results in an error. + self.socket.bind({ + port: self.options.bindPort, // unless otherwise specified, get a random port automatically + exclusive: true // you should not share the same port, otherwise yours packages will be screwed up between workers + }); } // We inherit from EventEmitter so that we can emit error events @@ -493,119 +498,125 @@ exports.Session = Session; // ~1000 seconds. This is OK since we only need to keep unique ID:s for in // flight packets and they should be safely timed out by then. -Session.prototype.requestId = function () { - var self = this, now = Date.now(); +Session.prototype.requestId = function() { + var self = this, + now = Date.now(); - if (!self.prevTs) { - self.prevTs = now; - self.counter = 0; - } + if (!self.prevTs) { + self.prevTs = now; + self.counter = 0; + } - if (now === self.prevTs) { - self.counter += 1; - if (self.counter > 1023) { - throw new Error('Request ID counter overflow. Adjust algorithm.'); - } - } else { - self.prevTs = now; - self.counter = 0; + if (now === self.prevTs) { + self.counter += 1; + if (self.counter > 1023) { + throw new Error('Request ID counter overflow. Adjust algorithm.'); } + } else { + self.prevTs = now; + self.counter = 0; + } - return ((now & 0x1fffff) << 10) + self.counter; + return ((now & 0x1fffff) << 10) + self.counter; }; // Send a message. Can be used after manually constructing a correct Packet structure. -Session.prototype.sendMsg = function (pkt, options, callback) { - var self = this, buf, reqid, retrans = 0; +Session.prototype.sendMsg = function(pkt, options, callback) { + var self = this, + buf, + reqid, + retrans = 0; - defaults(options, self.options); + defaults(options, self.options); - reqid = self.requestId(); - pkt.pdu.reqid = reqid; + reqid = self.requestId(); + pkt.pdu.reqid = reqid; - buf = encode(pkt); - - function transmit() { - if (!self.socket || !self.reqs[reqid]) { - // The socket has already been closed, perhaps due to an error that ocurred while a timeout - // was scheduled. We can't do anything about it now. - clearRequest(self.reqs, reqid); - return; - } else if (!options.timeouts[retrans]){ - // If there is no other configured retransmission attempt, we raise a final timeout error - clearRequest(self.reqs, reqid); - return callback(new Error('Timeout')); - } + buf = encode(pkt); - // Send the message. - self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) { - var entry = self.reqs[reqid]; - - if (err) { - clearRequest(self.reqs, reqid); - return callback(err); - } else if (entry) { - // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply. - entry.sendStamp = Date.now(); - entry.timeout = setTimeout(transmit, options.timeouts[retrans]); - retrans += 1; - } - }); + function transmit() { + if (!self.socket || !self.reqs[reqid]) { + // The socket has already been closed, perhaps due to an error that ocurred while a timeout + // was scheduled. We can't do anything about it now. + clearRequest(self.reqs, reqid); + return; + } else if (!options.timeouts[retrans]) { + // If there is no other configured retransmission attempt, we raise a final timeout error + clearRequest(self.reqs, reqid); + return callback(new Error('Timeout')); } - // Register the callback to call when we receive a reply. - self.reqs[reqid] = { callback: callback }; - // Transmit the message. - transmit(); + // Send the message. + self.socket.send(buf, 0, buf.length, options.port, options.host, function(err) { + var entry = self.reqs[reqid]; + + if (err) { + clearRequest(self.reqs, reqid); + return callback(err); + } else if (entry) { + // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply. + entry.sendStamp = Date.now(); + entry.timeout = setTimeout(transmit, options.timeouts[retrans]); + retrans += 1; + } + }); + } + + // Register the callback to call when we receive a reply. + self.reqs[reqid] = { callback: callback }; + // Transmit the message. + transmit(); }; // Shortcut to create a GetRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form. -Session.prototype.get = function (options, callback) { - var self = this, pkt; +Session.prototype.get = function(options, callback) { + var self = this, + pkt; - defaults(options, self.options); - parseOids(options); + defaults(options, self.options); + parseOids(options); - if (!options.oid) { - return callback(null, []); - } + if (!options.oid) { + return callback(null, []); + } - pkt = new Packet(); - pkt.community = options.community; - pkt.version = options.version; - pkt.pdu.varbinds[0].oid = options.oid; - self.sendMsg(pkt, options, callback); + pkt = new Packet(); + pkt.community = options.community; + pkt.version = options.version; + pkt.pdu.varbinds[0].oid = options.oid; + self.sendMsg(pkt, options, callback); }; // Shortcut to create a SetRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form, `options.value` to be an // integer and `options.type` to be asn1ber.T.Integer (2). -Session.prototype.set = function (options, callback) { - var self = this, pkt; - - defaults(options, self.options); - parseOids(options); - - if (!options.oid) { - throw new Error('Missing required option `oid`.'); - } else if (options.value === undefined) { - throw new Error('Missing required option `value`.'); - } else if (!options.type) { - throw new Error('Missing required option `type`.'); - } - - pkt = new Packet(); - pkt.community = options.community; - pkt.version = options.version; - pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU; - pkt.pdu.varbinds[0].oid = options.oid; - pkt.pdu.varbinds[0].type = options.type; - pkt.pdu.varbinds[0].value = options.value; - self.sendMsg(pkt, options, callback); +Session.prototype.set = function(options, callback) { + var self = this, + pkt; + + defaults(options, self.options); + parseOids(options); + + if (!options.oid) { + throw new Error('Missing required option `oid`.'); + } else if (options.value === undefined) { + throw new Error('Missing required option `value`.'); + } else if (!options.type) { + throw new Error('Missing required option `type`.'); + } + + pkt = new Packet(); + pkt.community = options.community; + pkt.version = options.version; + pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU; + pkt.pdu.varbinds[0].oid = options.oid; + pkt.pdu.varbinds[0].type = options.type; + pkt.pdu.varbinds[0].value = options.value; + self.sendMsg(pkt, options, callback); }; // Shortcut to get all OIDs in the `options.oids` array sequentially. The @@ -615,87 +626,90 @@ Session.prototype.set = function (options, callback) { // `options.abortOnError` is falsish (the default), any errors will be ignored // and any successfully retrieved values sent to the callback. -Session.prototype.getAll = function (options, callback) { - var self = this, results = [], - combinedTimeoutTimer = null, combinedTimeoutExpired = false; +Session.prototype.getAll = function(options, callback) { + var self = this, + results = [], + combinedTimeoutTimer = null, + combinedTimeoutExpired = false; - defaults(options, self.options, { abortOnError: false }); - parseOids(options); + defaults(options, self.options, { abortOnError: false }); + parseOids(options); - if (!options.oids || options.oids.length === 0) { - return callback(null, []); - } + if (!options.oids || options.oids.length === 0) { + return callback(null, []); + } - function getOne(c) { - var oid, pkt, m, vb; - - pkt = new Packet(); - pkt.community = options.community; - pkt.version = options.version; - pkt.pdu.varbinds = []; - - // Push up to 16 varbinds in the same message. - // The number 16 isn't really that magical, it's just a nice round - // number that usually seems to fit withing a single packet and gets - // accepted by the switches I've tested it on. - for (m = 0; m < 16 && c < options.oids.length; m++) { - vb = new VarBind(); - vb.oid = options.oids[c]; - pkt.pdu.varbinds.push(vb); - c++; - } + function getOne(c) { + var pkt, m, vb; - self.sendMsg(pkt, options, function (err, varbinds) { - if (combinedTimeoutExpired) { - return; - } - if (options.abortOnError && err) { - clearTimeout(combinedTimeoutTimer); - callback(err); - } else { - if (varbinds) { - results = results.concat(varbinds); - } - if (c < options.oids.length) { - getOne(c); - } else { - clearTimeout(combinedTimeoutTimer); - callback(null, results); - } - } - }); - } + pkt = new Packet(); + pkt.community = options.community; + pkt.version = options.version; + pkt.pdu.varbinds = []; - if (options.combinedTimeout) { - var combinedTimeoutEvent = function() { - combinedTimeoutExpired = true; - return callback(new Error('Timeout'), results); - }; - combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); + // Push up to 16 varbinds in the same message. + // The number 16 isn't really that magical, it's just a nice round + // number that usually seems to fit withing a single packet and gets + // accepted by the switches I've tested it on. + for (m = 0; m < 16 && c < options.oids.length; m++) { + vb = new VarBind(); + vb.oid = options.oids[c]; + pkt.pdu.varbinds.push(vb); + c++; } - getOne(0); + self.sendMsg(pkt, options, function(err, varbinds) { + if (combinedTimeoutExpired) { + return; + } + if (options.abortOnError && err) { + clearTimeout(combinedTimeoutTimer); + callback(err); + } else { + if (varbinds) { + results = results.concat(varbinds); + } + if (c < options.oids.length) { + getOne(c); + } else { + clearTimeout(combinedTimeoutTimer); + callback(null, results); + } + } + }); + } + + if (options.combinedTimeout) { + var combinedTimeoutEvent = function() { + combinedTimeoutExpired = true; + return callback(new Error('Timeout'), results); + }; + combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); + } + + getOne(0); }; // Shortcut to create a GetNextRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form. -Session.prototype.getNext = function (options, callback) { - var self = this, pkt; +Session.prototype.getNext = function(options, callback) { + var self = this, + pkt; - defaults(options, self.options); - parseOids(options); + defaults(options, self.options); + parseOids(options); - if (!options.oid) { - return callback(null, []); - } + if (!options.oid) { + return callback(null, []); + } - pkt = new Packet(); - pkt.community = options.community; - pkt.version = options.version; - pkt.pdu.type = 1; - pkt.pdu.varbinds[0].oid = options.oid; - self.sendMsg(pkt, options, callback); + pkt = new Packet(); + pkt.community = options.community; + pkt.version = options.version; + pkt.pdu.type = 1; + pkt.pdu.varbinds[0].oid = options.oid; + self.sendMsg(pkt, options, callback); }; // Shortcut to get all entries below the specified OID. @@ -703,85 +717,94 @@ Session.prototype.getNext = function (options, callback) { // varbinds that was collected, or with an error object. // Needs `options.oid` to be an OID in array form. -Session.prototype.getSubtree = function (options, callback) { - var self = this, vbs = [], - combinedTimeoutTimer = null, combinedTimeoutExpired = false; +Session.prototype.getSubtree = function(options, callback) { + var self = this, + vbs = [], + combinedTimeoutTimer = null, + combinedTimeoutExpired = false; - defaults(options, self.options); - parseOids(options); + defaults(options, self.options); + parseOids(options); - if (!options.oid) { - return callback(null, []); - } + if (!options.oid) { + return callback(null, []); + } - options.startOid = options.oid; + options.startOid = options.oid; - // Helper to check whether `oid` in inside the tree rooted at - // `root` or not. - function inTree(root, oid) { - var i; - if (oid.length <= root.length) { - return false; - } - for (i = 0; i < root.length; i++) { - if (oid[i] !== root[i]) { - return false; - } - } - return true; + // Helper to check whether `oid` in inside the tree rooted at + // `root` or not. + function inTree(root, oid) { + var i; + if (oid.length <= root.length) { + return false; } - - // Helper to handle the result of getNext and call the user's callback - // as appropriate. The callback will see one of the following patterns: - // - callback([an Error object], undefined) -- an error ocurred. - // - callback(null, [a Packet object]) -- data from under the tree. - // - callback(null, null) -- end of tree. - function result(error, varbinds) { - if (combinedTimeoutExpired) { - return; - } - if (error) { - clearTimeout(combinedTimeoutTimer); - callback(error); + for (i = 0; i < root.length; i++) { + if (oid[i] !== root[i]) { + return false; + } + } + return true; + } + + // Helper to handle the result of getNext and call the user's callback + // as appropriate. The callback will see one of the following patterns: + // - callback([an Error object], undefined) -- an error ocurred. + // - callback(null, [a Packet object]) -- data from under the tree. + // - callback(null, null) -- end of tree. + function result(error, varbinds) { + if (combinedTimeoutExpired) { + return; + } + if (error) { + clearTimeout(combinedTimeoutTimer); + callback(error); + } else { + if (inTree(options.startOid, varbinds[0].oid)) { + if ( + varbinds[0].value === 'endOfMibView' || + varbinds[0].value === 'noSuchObject' || + varbinds[0].value === 'noSuchInstance' + ) { + clearTimeout(combinedTimeoutTimer); + callback(null, vbs); + } else if ( + vbs.length && + compareOids(vbs.slice(-1)[0].oid, varbinds[0].oid) !== 1 + ) { + return callback(new Error('OID not increasing')); } else { - if (inTree(options.startOid, varbinds[0].oid)) { - if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') { - clearTimeout(combinedTimeoutTimer); - callback(null, vbs); - } else if (vbs.length && compareOids(vbs.slice(-1)[0].oid, varbinds[0].oid) !== 1) { - return callback(new Error('OID not increasing')); - } else { - vbs.push(varbinds[0]); - var next = { oid: varbinds[0].oid }; - defaults(next, options); - self.getNext(next, result); - } - } else { - clearTimeout(combinedTimeoutTimer); - callback(null, vbs); - } + vbs.push(varbinds[0]); + var next = { oid: varbinds[0].oid }; + defaults(next, options); + self.getNext(next, result); } + } else { + clearTimeout(combinedTimeoutTimer); + callback(null, vbs); + } } + } - if (options.combinedTimeout) { - var combinedTimeoutEvent = function() { - combinedTimeoutExpired = true; - return callback(new Error('Timeout'), vbs); - }; - combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); - } + if (options.combinedTimeout) { + var combinedTimeoutEvent = function() { + combinedTimeoutExpired = true; + return callback(new Error('Timeout'), vbs); + }; + combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); + } - self.getNext(options, result); + self.getNext(options, result); }; // Close the socket. Necessary to finish the event loop and exit the program. -Session.prototype.close = function () { - var self = this; - for (var reqid in self.reqs) { - if (self.reqs[reqid].callback) { - self.reqs[reqid].callback(new Error('Cancelled')); - } - clearRequest(self.reqs, reqid); +Session.prototype.close = function() { + var self = this; + for (var reqid in self.reqs) { + if (self.reqs[reqid].callback) { + self.reqs[reqid].callback(new Error('Cancelled')); } - this.socket.close(); + clearRequest(self.reqs, reqid); + } + this.socket.close(); };