net-snmp 3.24.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1947,6 +1947,8 @@ var myScalarProvider = {
1947
1947
  // e.g. can update the MIB data before responding to the request here
1948
1948
  mibRequest.done ();
1949
1949
  }
1950
+ // Note: handler is optional for scalar providers - if omitted,
1951
+ // mibRequest.done() is called automatically
1950
1952
  };
1951
1953
  var mib = agent.getMib ();
1952
1954
  mib.registerProvider (myScalarProvider);
@@ -2157,6 +2159,8 @@ objects`, below, for details.
2157
2159
  * `handler` *(optional)* - an optional callback function, which is called before the request to the
2158
2160
  MIB is made. This could update the MIB value(s) handled by this provider. If not given,
2159
2161
  the values are simply returned from (or set in) the MIB without any other processing.
2162
+ If no `handler` is provided, the system automatically calls `mibRequest.done()` to complete
2163
+ the request, making both scalar and table providers fully functional without explicit handlers.
2160
2164
  The callback function takes a `MibRequest` instance, which has a `done()` function. This
2161
2165
  must be called when finished processing the request. To signal an error, give a single error object
2162
2166
  in the form of `{errorStatus: <status>}`, where `<status>` is a value from ErrorStatus e.g.
@@ -2704,6 +2708,79 @@ to both register the provider on its internal `Mib` object, *and* send a Registe
2704
2708
  agent for the provider's MIB region. The latter step is skipped if registering the provider directly
2705
2709
  on the MIB object.
2706
2710
 
2711
+ ## Simplified Provider Example
2712
+
2713
+ AgentX subagents provide automatic request handling for both scalar and table providers, making them simple to implement:
2714
+
2715
+ ```js
2716
+ // Both scalars and tables work with automatic request handling (no explicit handlers needed)
2717
+ var stringProvider = {
2718
+ name: "scalarString",
2719
+ type: snmp.MibProviderType.Scalar,
2720
+ oid: "1.3.6.1.4.1.8072.9999.9999.1",
2721
+ scalarType: snmp.ObjectType.OctetString,
2722
+ maxAccess: snmp.MaxAccess["read-write"]
2723
+ // No handler required - automatic fallback will call mibRequest.done()
2724
+ };
2725
+
2726
+ var intProvider = {
2727
+ name: "scalarInt",
2728
+ type: snmp.MibProviderType.Scalar,
2729
+ oid: "1.3.6.1.4.1.8072.9999.9999.3",
2730
+ scalarType: snmp.ObjectType.Integer,
2731
+ maxAccess: snmp.MaxAccess["read-write"]
2732
+ // No handler required - automatic fallback will call mibRequest.done()
2733
+ };
2734
+
2735
+ // Table providers also work without explicit handlers
2736
+ var tableProvider = {
2737
+ name: "smallIfTable",
2738
+ type: snmp.MibProviderType.Table,
2739
+ oid: "1.3.6.1.4.1.8072.9999.9999.2",
2740
+ maxAccess: snmp.MaxAccess['not-accessible'],
2741
+ tableColumns: [
2742
+ {
2743
+ number: 1,
2744
+ name: "ifIndex",
2745
+ type: snmp.ObjectType.Integer,
2746
+ maxAccess: snmp.MaxAccess['read-only']
2747
+ },
2748
+ {
2749
+ number: 2,
2750
+ name: "ifDescr",
2751
+ type: snmp.ObjectType.OctetString,
2752
+ maxAccess: snmp.MaxAccess['read-write']
2753
+ }
2754
+ ],
2755
+ tableIndex: [
2756
+ {
2757
+ columnName: "ifIndex"
2758
+ }
2759
+ ]
2760
+ // No handler required - automatic fallback works for tables too!
2761
+ };
2762
+
2763
+ // Register providers and set values
2764
+ subagent.registerProvider(stringProvider, function(error, data) {
2765
+ if (!error) {
2766
+ subagent.getMib().setScalarValue("scalarString", "Hello World!");
2767
+ }
2768
+ });
2769
+
2770
+ subagent.registerProvider(intProvider, function(error, data) {
2771
+ if (!error) {
2772
+ subagent.getMib().setScalarValue("scalarInt", 42);
2773
+ }
2774
+ });
2775
+
2776
+ subagent.registerProvider(tableProvider, function(error, data) {
2777
+ if (!error) {
2778
+ subagent.getMib().addTableRow("smallIfTable", [1, "lo"]);
2779
+ subagent.getMib().addTableRow("smallIfTable", [2, "eth0"]);
2780
+ }
2781
+ });
2782
+ ```
2783
+
2707
2784
  ## snmp.createSubagent (options)
2708
2785
 
2709
2786
  The `createSubagent ()` function instantiates and returns an instance of the `Subagent`
@@ -2764,6 +2841,11 @@ subsequent `Response` PDU from the master to the `Register` PDU. This is not to
2764
2841
  with the `handler` optional callback on the provider definition, which is invoked for any
2765
2842
  "request processing" PDU received by the subagent for MIB objects in the registered MIB region.
2766
2843
 
2844
+ **Note for AgentX subagents**: Neither scalar nor table providers require explicit `handler` functions.
2845
+ If no `handler` is provided, the subagent automatically calls `mibRequest.done()` to complete the request,
2846
+ allowing both scalar and table values to be served directly from the MIB without additional processing.
2847
+ This greatly simplifies provider definitions - you only need to specify the structure and `maxAccess` properties.
2848
+
2767
2849
  ## subagent.unregisterProvider (name, callback)
2768
2850
 
2769
2851
  Unregisters a previously registered MIB region by the supplied name of the provider. Sends
@@ -2814,6 +2896,48 @@ Sends a "ping" to the master agent using a `Ping` PDU, to confirm that the maste
2814
2896
  responsive. The supplied `callback` is called on reception of the subsequent
2815
2897
  `Response` PDU from the master to the `Ping` PDU.
2816
2898
 
2899
+ ## subagent.setBulkSetHandler (callback)
2900
+
2901
+ Sets a bulk set handler for the subagent that will be called for both **TestSet** and **CommitSet**
2902
+ phases of SNMP SET operations. This provides atomic validation capabilities, allowing you to validate
2903
+ all varbinds as a complete set before committing any changes.
2904
+
2905
+ The handler callback function receives two arguments:
2906
+ * `testSet` - A boolean indicating the phase: `true` for TestSet phase, `false` for CommitSet phase
2907
+ * `varbinds` - An array of all varbinds in the SET request
2908
+
2909
+ During the TestSet phase (`testSet = true`), you should validate all the requested changes but not
2910
+ apply them. During the CommitSet phase (`testSet = false`), you should apply the previously validated changes.
2911
+
2912
+ Example usage:
2913
+
2914
+ ```js
2915
+ subagent.setBulkSetHandler(function(testSet, varbinds) {
2916
+ if (testSet) {
2917
+ // TestSet phase - validate all varbinds atomically
2918
+ console.log("Validating SET operation with", varbinds.length, "varbinds");
2919
+ for (let varbind of varbinds) {
2920
+ // Perform validation logic here
2921
+ if (!isValidValue(varbind.oid, varbind.value)) {
2922
+ throw new Error("Invalid value for " + varbind.oid);
2923
+ }
2924
+ }
2925
+ console.log("All varbinds validated successfully");
2926
+ } else {
2927
+ // CommitSet phase - apply the changes
2928
+ console.log("Committing SET operation");
2929
+ for (let varbind of varbinds) {
2930
+ // Apply the validated changes here
2931
+ applyValue(varbind.oid, varbind.value);
2932
+ }
2933
+ console.log("All changes committed");
2934
+ }
2935
+ });
2936
+ ```
2937
+
2938
+ This approach ensures atomic SET operations where either all varbinds in a request succeed or all fail,
2939
+ maintaining data consistency across your MIB implementation.
2940
+
2817
2941
  ## subagent.on ("close", callback)
2818
2942
 
2819
2943
  The `close` event is emitted by the subagent when its underlying TCP socket is closed.
@@ -3569,6 +3693,10 @@ Example programs are included under the module's `example` directory.
3569
3693
 
3570
3694
  * Improve USM error handling compliance with RFC 3414
3571
3695
 
3696
+ # Version 3.25.0 - 28/08/2025
3697
+
3698
+ * Add separate AgentX subagent TestSet and CommitSet phases
3699
+
3572
3700
  # License
3573
3701
 
3574
3702
  Copyright (c) 2020 Mark Abrahams <mark@abrahams.co.nz>
package/eslint.config.js CHANGED
@@ -23,6 +23,8 @@ module.exports = [
23
23
  setTimeout: 'readonly',
24
24
  clearInterval: 'readonly',
25
25
  clearTimeout: 'readonly',
26
+ setImmediate: 'readonly',
27
+ clearImmediate: 'readonly',
26
28
 
27
29
  // Mocha globals
28
30
  describe: 'readonly',
@@ -15,33 +15,39 @@ var stringProvider = {
15
15
  name: "scalarString",
16
16
  type: snmp.MibProviderType.Scalar,
17
17
  oid: "1.3.6.1.4.1.8072.9999.9999.1",
18
- scalarType: snmp.ObjectType.OctetString
18
+ scalarType: snmp.ObjectType.OctetString,
19
+ maxAccess: snmp.MaxAccess["read-write"]
19
20
  };
20
21
  var intProvider = {
21
22
  name: "scalarInt",
22
23
  type: snmp.MibProviderType.Scalar,
23
24
  oid: "1.3.6.1.4.1.8072.9999.9999.3",
24
- scalarType: snmp.ObjectType.Integer
25
+ scalarType: snmp.ObjectType.Integer,
26
+ maxAccess: snmp.MaxAccess["read-write"]
25
27
  };
26
28
  var tableProvider = {
27
29
  name: "smallIfTable",
28
30
  type: snmp.MibProviderType.Table,
29
31
  oid: "1.3.6.1.4.1.8072.9999.9999.2",
32
+ maxAccess: snmp.MaxAccess['not-accessible'],
30
33
  tableColumns: [
31
34
  {
32
35
  number: 1,
33
36
  name: "ifIndex",
34
- type: snmp.ObjectType.Integer
37
+ type: snmp.ObjectType.Integer,
38
+ maxAccess: snmp.MaxAccess['read-only']
35
39
  },
36
40
  {
37
41
  number: 2,
38
42
  name: "ifDescr",
39
- type: snmp.ObjectType.OctetString
43
+ type: snmp.ObjectType.OctetString,
44
+ maxAccess: snmp.MaxAccess['read-write']
40
45
  },
41
46
  {
42
47
  number: 3,
43
48
  name: "ifType",
44
- type: snmp.ObjectType.Integer
49
+ type: snmp.ObjectType.Integer,
50
+ maxAccess: snmp.MaxAccess['read-only']
45
51
  }
46
52
  ],
47
53
  tableIndex: [
@@ -49,10 +55,7 @@ var tableProvider = {
49
55
  columnName: "ifIndex"
50
56
  }
51
57
  ],
52
- handler: function (mibRequest) {
53
- // e.g. can update the table before responding to the request here
54
- mibRequest.done ();
55
- }
58
+
56
59
  };
57
60
 
58
61
  agent.open(function (error, data) {
@@ -66,9 +69,41 @@ agent.open(function (error, data) {
66
69
  agent.registerProvider (tableProvider, null);
67
70
  agent.getMib ().addTableRow ("smallIfTable", [1, "lo", 24]);
68
71
  agent.getMib ().addTableRow ("smallIfTable", [2, "eth0", 6]);
72
+
73
+ console.log("AgentX subagent ready and providers registered successfully.");
74
+
69
75
  agent.on("close", function() {
70
76
  console.log ("Subagent socket closed");
71
77
  });
78
+
79
+ // Handle graceful shutdown on Ctrl-C
80
+ process.on('SIGINT', function() {
81
+ console.log('\nReceived SIGINT. Cleaning up...');
82
+
83
+ // Unregister all providers
84
+ try {
85
+ agent.unregisterProvider("scalarString", function(err, data) {
86
+ if (err) console.error("Error unregistering scalarString:", err);
87
+ });
88
+ agent.unregisterProvider("scalarInt", function(err, data) {
89
+ if (err) console.error("Error unregistering scalarInt:", err);
90
+ });
91
+ agent.unregisterProvider("smallIfTable", function(err, data) {
92
+ if (err) console.error("Error unregistering smallIfTable:", err);
93
+ });
94
+ } catch (e) {
95
+ console.error("Error during unregistration:", e.message);
96
+ }
97
+
98
+ // Close the agent after a brief delay to allow unregistrations to complete
99
+ setTimeout(function() {
100
+ agent.close(function(err) {
101
+ if (err) console.error("Error closing agent:", err);
102
+ console.log("Agent closed gracefully");
103
+ process.exit(0);
104
+ });
105
+ }, 100);
106
+ });
72
107
  }
73
108
  });
74
109
 
package/index.js CHANGED
@@ -213,6 +213,8 @@ var AgentXPduType = {
213
213
  17: "RemoveAgentCaps",
214
214
  18: "Response"
215
215
  };
216
+ const agentXPduTypesRequiringReadAccess = [ 5, 6, 7];
217
+ const agentXPduTypesRequiringWriteAccess = [ 8, 9, 10, 11, 14, 15, 16, 17 ];
216
218
 
217
219
  _expandConstantObject (AgentXPduType);
218
220
 
@@ -3983,17 +3985,9 @@ MibNode.prototype.getConstraintsFromProvider = function () {
3983
3985
  }
3984
3986
  };
3985
3987
 
3986
- MibNode.prototype.setValue = function (typeFromSet, valueFromSet) {
3988
+ MibNode.prototype.validateValue = function (typeFromSet, valueFromSet) {
3987
3989
  const constraints = this.getConstraintsFromProvider ();
3988
- try {
3989
- const castValue = ObjectTypeUtil.castSetValue (typeFromSet, valueFromSet, constraints);
3990
- this.value = castValue;
3991
- return true;
3992
- } catch (error) {
3993
- console.error ("Error setting value: " + error.message);
3994
- return false;
3995
- }
3996
-
3990
+ return ObjectTypeUtil.isValid (typeFromSet, valueFromSet, constraints);
3997
3991
  };
3998
3992
 
3999
3993
  MibNode.prototype.getInstanceNodeForTableRow = function () {
@@ -5253,11 +5247,12 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5253
5247
  var varbindsLength = requestPdu.varbinds.length;
5254
5248
  var responsePdu = requestPdu.getResponsePduForRequest ();
5255
5249
  var mibRequests = [];
5256
- var handlers = [];
5257
5250
  var createResult = [];
5258
5251
  var oldValues = [];
5259
5252
  var securityName = requestMessage.version == Version3 ? requestMessage.user.name : requestMessage.community;
5260
5253
 
5254
+ const isSetRequest = requestPdu.type === PduType.SetRequest;
5255
+
5261
5256
  for ( let i = 0; i < requestPdu.varbinds.length; i++ ) {
5262
5257
  let instanceNode = this.mib.lookup (requestPdu.varbinds[i].oid);
5263
5258
  let providerNode;
@@ -5287,7 +5282,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5287
5282
  });
5288
5283
  const ancestorProvider = this.mib.getAncestorProviderFromOid(requestPdu.varbinds[i].oid);
5289
5284
  if ( ancestorProvider ) {
5290
- handlers[i] = function getNsiHandler (mibRequestForNsi) {
5285
+ mibRequests[i].handler = function getNsiHandler (mibRequestForNsi) {
5291
5286
  mibRequestForNsi.done ({
5292
5287
  errorStatus: ErrorStatus.NoError,
5293
5288
  type: ObjectType.NoSuchInstance,
@@ -5295,7 +5290,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5295
5290
  });
5296
5291
  };
5297
5292
  } else {
5298
- handlers[i] = function getNsoHandler (mibRequestForNso) {
5293
+ mibRequests[i].handler = function getNsoHandler (mibRequestForNso) {
5299
5294
  mibRequestForNso.done ({
5300
5295
  errorStatus: ErrorStatus.NoError,
5301
5296
  type: ObjectType.NoSuchObject,
@@ -5310,7 +5305,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5310
5305
  operation: requestPdu.type,
5311
5306
  oid: requestPdu.varbinds[i].oid
5312
5307
  });
5313
- handlers[i] = function getNsiHandler (mibRequestForNsi) {
5308
+ mibRequests[i].handler = function getNsiHandler (mibRequestForNsi) {
5314
5309
  mibRequestForNsi.done ({
5315
5310
  errorStatus: ErrorStatus.NoError,
5316
5311
  type: ObjectType.NoSuchInstance,
@@ -5323,7 +5318,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5323
5318
  operation: requestPdu.type,
5324
5319
  oid: requestPdu.varbinds[i].oid
5325
5320
  });
5326
- handlers[i] = function getRanaHandler (mibRequestForRana) {
5321
+ mibRequests[i].handler = function getRanaHandler (mibRequestForRana) {
5327
5322
  mibRequestForRana.done ({
5328
5323
  errorStatus: ErrorStatus.NoAccess,
5329
5324
  type: ObjectType.Null,
@@ -5337,14 +5332,14 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5337
5332
  operation: requestPdu.type,
5338
5333
  oid: requestPdu.varbinds[i].oid
5339
5334
  });
5340
- handlers[i] = function getAccessDeniedHandler (mibRequestForAccessDenied) {
5335
+ mibRequests[i].handler = function getAccessDeniedHandler (mibRequestForAccessDenied) {
5341
5336
  mibRequestForAccessDenied.done ({
5342
5337
  errorStatus: ErrorStatus.NoAccess,
5343
5338
  type: ObjectType.Null,
5344
5339
  value: null
5345
5340
  });
5346
5341
  };
5347
- } else if ( requestPdu.type === PduType.SetRequest &&
5342
+ } else if ( isSetRequest &&
5348
5343
  providerNode.provider.type == MibProviderType.Table &&
5349
5344
  typeof (rowStatusColumn = providerNode.provider.tableColumns.reduce(
5350
5345
  (acc, current) => current.rowStatus ? current.number : acc, null )) == "number" &&
@@ -5380,7 +5375,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5380
5375
  operation: requestPdu.type,
5381
5376
  oid: requestPdu.varbinds[i].oid
5382
5377
  });
5383
- handlers[i] = getIcsHandler;
5378
+ mibRequests[i].handler = getIcsHandler;
5384
5379
  }
5385
5380
  break;
5386
5381
 
@@ -5394,7 +5389,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5394
5389
  operation: requestPdu.type,
5395
5390
  oid: requestPdu.varbinds[i].oid
5396
5391
  });
5397
- handlers[i] = getIcsHandler;
5392
+ mibRequests[i].handler = getIcsHandler;
5398
5393
  }
5399
5394
  break;
5400
5395
 
@@ -5415,16 +5410,16 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5415
5410
  operation: requestPdu.type,
5416
5411
  oid: requestPdu.varbinds[i].oid
5417
5412
  });
5418
- handlers[i] = getIcsHandler;
5413
+ mibRequests[i].handler = getIcsHandler;
5419
5414
  break;
5420
5415
  }
5421
5416
  }
5422
5417
 
5423
- if ( requestPdu.type === PduType.SetRequest && ! createResult[i] ) {
5418
+ if ( isSetRequest && ! createResult[i] ) {
5424
5419
  oldValues[i] = instanceNode.value;
5425
5420
  }
5426
5421
 
5427
- if ( ! handlers[i] ) {
5422
+ if ( ! mibRequests[i] ) {
5428
5423
  mibRequests[i] = new MibRequest ({
5429
5424
  operation: requestPdu.type,
5430
5425
  providerNode: providerNode,
@@ -5432,38 +5427,67 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5432
5427
  oid: requestPdu.varbinds[i].oid
5433
5428
  });
5434
5429
 
5435
- if ( requestPdu.type == PduType.SetRequest ) {
5436
- mibRequests[i].setType = requestPdu.varbinds[i].type;
5437
- mibRequests[i].setValue = requestPdu.varbinds[i].requestValue || requestPdu.varbinds[i].value;
5430
+ mibRequests[i].handler = providerNode.provider.handler;
5431
+
5432
+ if ( isSetRequest ) {
5433
+ mibRequests[i].setType = instanceNode.valueType;
5434
+ mibRequests[i].setValue = requestPdu.varbinds[i].requestValue ?? requestPdu.varbinds[i].value;
5435
+ try {
5436
+ mibRequests[i].setValue =
5437
+ ObjectTypeUtil.castSetValue (mibRequests[i].setType, mibRequests[i].setValue);
5438
+
5439
+ if ( ! mibRequests[i].instanceNode.validateValue (mibRequests[i].setType, mibRequests[i].setValue) ) {
5440
+ mibRequests[i].handler = function badValueHandler (request) {
5441
+ request.done ({
5442
+ errorStatus: ErrorStatus.BadValue,
5443
+ type: ObjectType.Null,
5444
+ value: null,
5445
+ });
5446
+ };
5447
+ }
5448
+ } catch (e) {
5449
+ debug('Invalid value for type', e, mibRequests[i]);
5450
+ mibRequests[i].handler = function wrongTypeHandler (request) {
5451
+ request.done ({
5452
+ errorStatus: ErrorStatus.WrongType,
5453
+ type: ObjectType.Null,
5454
+ value: null,
5455
+ });
5456
+ };
5457
+ }
5438
5458
  }
5439
- handlers[i] = providerNode.provider.handler;
5440
5459
  }
5441
5460
  }
5442
5461
 
5443
5462
  (function (savedIndex) {
5444
- let responseVarbind;
5445
- mibRequests[savedIndex].done = function (error) {
5463
+ const mibRequest = mibRequests[savedIndex];
5464
+ mibRequest.done = function (error) {
5465
+ mibRequest.error = error ?? { errorStatus: ErrorStatus.NoError };
5466
+ let responseVarbind;
5446
5467
  let rowIndex = null;
5447
5468
  let row = null;
5448
5469
  let deleted = false;
5449
5470
  let column = -1;
5450
5471
  responseVarbind = {
5451
- oid: mibRequests[savedIndex].oid
5472
+ oid: mibRequest.oid
5452
5473
  };
5453
5474
  if ( error ) {
5454
- if ( (typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError) && error.errorStatus != ErrorStatus.NoError ) {
5475
+ if ( (typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError) &&
5476
+ error.errorStatus != ErrorStatus.NoError ) {
5455
5477
  responsePdu.errorStatus = error.errorStatus;
5456
5478
  responsePdu.errorIndex = savedIndex + 1;
5457
5479
  }
5458
5480
  responseVarbind.type = error.type || ObjectType.Null;
5459
- responseVarbind.value = error.value || null;
5481
+ responseVarbind.value = error.value ?? null;
5460
5482
  //responseVarbind.errorStatus: error.errorStatus
5461
5483
  if ( error.errorStatus != ErrorStatus.NoError ) {
5462
5484
  responseVarbind.errorStatus = error.errorStatus;
5463
5485
  }
5464
5486
  } else {
5465
- let provider = providerNode ? providerNode.provider : null;
5466
- let providerName = provider ? provider.name : null;
5487
+ const instanceNode = mibRequest.instanceNode;
5488
+ const providerNode = mibRequest.providerNode;
5489
+ const provider = providerNode ? providerNode.provider : null;
5490
+ const providerName = provider ? provider.name : null;
5467
5491
  let subOid;
5468
5492
  let subAddr;
5469
5493
  if ( providerNode && providerNode.provider && providerNode.provider.type == MibProviderType.Table ) {
@@ -5474,7 +5498,7 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5474
5498
  rowIndex = Mib.getRowIndexFromOid( subAddr.join("."), provider.tableIndex );
5475
5499
  row = me.mib.getTableRowCells ( providerName, rowIndex );
5476
5500
  }
5477
- if ( requestPdu.type == PduType.SetRequest ) {
5501
+ if ( isSetRequest && mibRequest.commitSet ) {
5478
5502
  // Is this a RowStatus column with a value of 6 (delete)?
5479
5503
  let rowStatusColumn = provider.type == MibProviderType.Table
5480
5504
  ? provider.tableColumns.reduce( (acc, current) => current.rowStatus ? current.number : acc, null )
@@ -5498,26 +5522,16 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5498
5522
 
5499
5523
  } else {
5500
5524
  // No special handling required. Just save the new value.
5501
- const setResult = mibRequests[savedIndex].instanceNode.setValue (
5502
- requestPdu.varbinds[savedIndex].type,
5503
- requestPdu.varbinds[savedIndex].value
5504
- );
5505
- if ( ! setResult ) {
5506
- if ( typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError ) {
5507
- responsePdu.errorStatus = ErrorStatus.WrongValue;
5508
- responsePdu.errorIndex = savedIndex + 1;
5509
- }
5510
- responseVarbind.errorStatus = ErrorStatus.WrongValue;
5511
- }
5525
+ mibRequest.instanceNode.value = mibRequest.setValue;
5512
5526
  }
5513
5527
  }
5514
5528
  if ( ( requestPdu.type == PduType.GetNextRequest || requestPdu.type == PduType.GetBulkRequest ) &&
5515
5529
  requestPdu.varbinds[savedIndex].type == ObjectType.EndOfMibView ) {
5516
5530
  responseVarbind.type = ObjectType.EndOfMibView;
5517
5531
  } else {
5518
- responseVarbind.type = mibRequests[savedIndex].instanceNode.valueType;
5532
+ responseVarbind.type = mibRequest.instanceNode.valueType;
5519
5533
  }
5520
- responseVarbind.value = mibRequests[savedIndex].instanceNode.value;
5534
+ responseVarbind.value = mibRequest.instanceNode.value;
5521
5535
  if ( responseVarbind.value === undefined || responseVarbind.value === null ) {
5522
5536
  responseVarbind.type = ObjectType.NoSuchInstance;
5523
5537
  }
@@ -5528,16 +5542,19 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5528
5542
  if ( requestPdu.type == PduType.GetNextRequest || requestPdu.type == PduType.GetNextRequest ) {
5529
5543
  responseVarbind.previousOid = requestPdu.varbinds[savedIndex].previousOid;
5530
5544
  }
5531
- if ( requestPdu.type == PduType.SetRequest ) {
5545
+ const isTestSet = mibRequest.testSet;
5546
+ if ( isSetRequest ) {
5547
+ if ( mibRequest.testSet ) {
5548
+ delete mibRequest.testSet;
5549
+ mibRequest.commitSet = true;
5550
+ if ( mibRequest.error.errorStatus === ErrorStatus.NoError )
5551
+ delete mibRequest.error;
5552
+ }
5532
5553
  if ( oldValues[savedIndex] !== undefined ) {
5533
5554
  responseVarbind.oldValue = oldValues[savedIndex];
5534
5555
  }
5535
- responseVarbind.requestType = requestPdu.varbinds[savedIndex].type;
5536
- if ( requestPdu.varbinds[savedIndex].requestValue ) {
5537
- responseVarbind.requestValue = ObjectTypeUtil.castSetValue (requestPdu.varbinds[savedIndex].type, requestPdu.varbinds[savedIndex].requestValue);
5538
- } else {
5539
- responseVarbind.requestValue = ObjectTypeUtil.castSetValue (requestPdu.varbinds[savedIndex].type, requestPdu.varbinds[savedIndex].value);
5540
- }
5556
+ responseVarbind.requestType = mibRequests[savedIndex].setType;
5557
+ responseVarbind.requestValue = mibRequests[savedIndex].setValue;
5541
5558
  }
5542
5559
  if ( createResult[savedIndex] ) {
5543
5560
  responseVarbind.autoCreated = true;
@@ -5555,16 +5572,56 @@ Agent.prototype.request = function (socket, requestMessage, rinfo) {
5555
5572
  }
5556
5573
  me.setSingleVarbind (responsePdu, savedIndex, responseVarbind);
5557
5574
  if ( ++varbindsCompleted == varbindsLength) {
5575
+ // If all varbind have been tested, apply
5576
+ // the handlers again in commit mode.
5577
+ if (isTestSet && !responsePdu.errorIndex) {
5578
+ varbindsCompleted = 0;
5579
+ setImmediate(() => applySetHandlers(/*testSet=*/false));
5580
+ return;
5581
+ }
5558
5582
  me.sendResponse.call (me, socket, rinfo, requestMessage, responsePdu);
5559
5583
  }
5560
5584
  };
5585
+ if ( isSetRequest )
5586
+ mibRequest.testSet = true;
5561
5587
  })(i);
5562
- if ( handlers[i] ) {
5563
- handlers[i] (mibRequests[i]);
5564
- } else {
5565
- mibRequests[i].done ();
5566
- }
5567
5588
  }
5589
+ const applyHandlers = testSet => {
5590
+ for ( const mibRequest of mibRequests ) {
5591
+ if ( mibRequest.error === undefined && testSet === !!mibRequest.testSet ) {
5592
+ if ( mibRequest.handler ) {
5593
+ mibRequest.handler (mibRequest);
5594
+ } else {
5595
+ mibRequest.done ();
5596
+ }
5597
+ }
5598
+ }
5599
+ };
5600
+ const applySetHandlers = testSet => {
5601
+ if ( this.bulkSetHandler ) {
5602
+ const errorStatus = this.bulkSetHandler( mibRequests, this.mib, testSet ) ?? ErrorStatus.NoError;
5603
+ if ( errorStatus !== ErrorStatus.NoError ) {
5604
+ for ( const mibRequest of mibRequests ) {
5605
+ if ( mibRequest.error === undefined ) {
5606
+ mibRequest.done ({
5607
+ errorStatus,
5608
+ type: ObjectType.Null,
5609
+ value: null
5610
+ });
5611
+ }
5612
+ }
5613
+ }
5614
+ }
5615
+ applyHandlers(testSet);
5616
+ };
5617
+ if ( isSetRequest )
5618
+ applySetHandlers(/*testSet=*/true);
5619
+ else
5620
+ applyHandlers(false);
5621
+ };
5622
+
5623
+ Agent.prototype.setBulkSetHandler = function setBulkSetHandler(cb) {
5624
+ this.bulkSetHandler = cb;
5568
5625
  };
5569
5626
 
5570
5627
  Agent.prototype.getRequest = function (socket, requestMessage, rinfo) {
@@ -5915,6 +5972,13 @@ AgentXPdu.createFromVariables = function (vars) {
5915
5972
  pdu.index = vars.index || 0;
5916
5973
  pdu.varbinds = vars.varbinds || null;
5917
5974
  break;
5975
+ case AgentXPduType.TestSet:
5976
+ pdu.varbinds = vars.varbinds || null;
5977
+ break;
5978
+ case AgentXPduType.CommitSet:
5979
+ case AgentXPduType.UndoSet:
5980
+ case AgentXPduType.CleanupSet:
5981
+ break;
5918
5982
  default:
5919
5983
  // unsupported PDU type - should never happen as we control these
5920
5984
  throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType
@@ -6339,35 +6403,39 @@ Subagent.prototype.onMsg = function (buffer, rinfo) {
6339
6403
  debug ("Received AgentX " + AgentXPduType[pdu.pduType] + " PDU");
6340
6404
  debug (pdu);
6341
6405
 
6342
- switch (pdu.pduType) {
6343
- case AgentXPduType.Response:
6344
- this.response (pdu);
6345
- break;
6346
- case AgentXPduType.Get:
6347
- this.getRequest (pdu);
6348
- break;
6349
- case AgentXPduType.GetNext:
6350
- this.getNextRequest (pdu);
6351
- break;
6352
- case AgentXPduType.GetBulk:
6353
- this.getBulkRequest (pdu);
6354
- break;
6355
- case AgentXPduType.TestSet:
6356
- this.testSet (pdu);
6357
- break;
6358
- case AgentXPduType.CommitSet:
6359
- this.commitSet (pdu);
6360
- break;
6361
- case AgentXPduType.UndoSet:
6362
- this.undoSet (pdu);
6363
- break;
6364
- case AgentXPduType.CleanupSet:
6365
- this.cleanupSet (pdu);
6366
- break;
6367
- default:
6368
- // Unknown PDU type - shouldn't happen as master agents shouldn't send administrative PDUs
6369
- throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType
6370
- + "' in request");
6406
+ try {
6407
+ switch (pdu.pduType) {
6408
+ case AgentXPduType.Response:
6409
+ this.response (pdu);
6410
+ break;
6411
+ case AgentXPduType.Get:
6412
+ this.getRequest (pdu);
6413
+ break;
6414
+ case AgentXPduType.GetNext:
6415
+ this.getNextRequest (pdu);
6416
+ break;
6417
+ case AgentXPduType.GetBulk:
6418
+ this.getBulkRequest (pdu);
6419
+ break;
6420
+ case AgentXPduType.TestSet:
6421
+ this.testSet (pdu);
6422
+ break;
6423
+ case AgentXPduType.CommitSet:
6424
+ this.commitSet (pdu);
6425
+ break;
6426
+ case AgentXPduType.UndoSet:
6427
+ this.undoSet (pdu);
6428
+ break;
6429
+ case AgentXPduType.CleanupSet:
6430
+ this.cleanupSet (pdu);
6431
+ break;
6432
+ default:
6433
+ // Unknown PDU type - shouldn't happen as master agents shouldn't send administrative PDUs
6434
+ throw new RequestInvalidError ("Unknown PDU type '" + pdu.pduType
6435
+ + "' in request");
6436
+ }
6437
+ } catch (e) {
6438
+ console.error(e);
6371
6439
  }
6372
6440
  };
6373
6441
 
@@ -6406,82 +6474,141 @@ Subagent.prototype.response = function (pdu) {
6406
6474
  }
6407
6475
  };
6408
6476
 
6477
+ Subagent.prototype.isAllowed = function (pduType, provider, instanceNode) {
6478
+ const requestedAccess =
6479
+ agentXPduTypesRequiringReadAccess.includes(pduType)? MaxAccess["read-only"] :
6480
+ agentXPduTypesRequiringWriteAccess.includes(pduType)? MaxAccess["read-write"] :
6481
+ undefined;
6482
+
6483
+ if (requestedAccess === undefined)
6484
+ return true;
6485
+
6486
+ if (provider.type === MibProviderType.Scalar)
6487
+ return provider.maxAccess >= requestedAccess;
6488
+
6489
+ // It's a table column. Use that column's maxAccess.
6490
+ const column = instanceNode.getTableColumnFromInstanceNode();
6491
+
6492
+ // In the typical case, we could use (column - 1) to index
6493
+ // into tableColumns to get to the correct entry. There is no
6494
+ // guarantee, however, that column numbers in the OID are
6495
+ // necessarily consecutive; theoretically some could be
6496
+ // missing. We'll therefore play it safe and search for the
6497
+ // specified column entry.
6498
+
6499
+ const columnEntry = provider.tableColumns.find(entry => entry.number === column);
6500
+ const maxAccess = columnEntry ? columnEntry.maxAccess || MaxAccess['not-accessible'] : MaxAccess['not-accessible'];
6501
+ return maxAccess >= requestedAccess;
6502
+ };
6503
+
6504
+
6409
6505
  Subagent.prototype.request = function (pdu, requestVarbinds) {
6410
6506
  const me = this;
6411
6507
  const varbindsLength = requestVarbinds.length;
6412
6508
  const responseVarbinds = [];
6413
6509
  const responsePdu = pdu.getResponsePduForRequest ();
6510
+ const mibRequests = [];
6414
6511
  let varbindsCompleted = 0;
6512
+ let firstVarbindError;
6513
+ const isTestSet = pdu.pduType == AgentXPduType.TestSet;
6514
+ const isSetRequest = isTestSet ||
6515
+ pdu.pduType == AgentXPduType.CommitSet ||
6516
+ pdu.pduType == AgentXPduType.UndoSet;
6415
6517
 
6416
6518
  for ( let i = 0; i < varbindsLength; i++ ) {
6417
6519
  const requestVarbind = requestVarbinds[i];
6418
- var instanceNode = this.mib.lookup (requestVarbind.oid);
6419
- var providerNode;
6420
- var mibRequest;
6421
- var handler;
6422
- var responseVarbindType;
6520
+ const instanceNode = this.mib.lookup (requestVarbind.oid);
6521
+ let providerNode;
6522
+ let responseVarbindType;
6423
6523
 
6424
6524
  if ( ! instanceNode ) {
6425
- mibRequest = new MibRequest ({
6525
+ mibRequests[i] = new MibRequest ({
6426
6526
  operation: pdu.pduType,
6427
6527
  oid: requestVarbind.oid
6428
6528
  });
6429
- handler = function getNsoHandler (mibRequestForNso) {
6430
- mibRequestForNso.done ({
6529
+ mibRequests[i].error = {
6431
6530
  errorStatus: ErrorStatus.NoError,
6432
6531
  errorIndex: 0,
6433
6532
  type: ObjectType.NoSuchObject,
6434
6533
  value: null
6435
- });
6436
6534
  };
6437
6535
  } else {
6438
6536
  providerNode = this.mib.getProviderNodeForInstance (instanceNode);
6439
6537
  if ( ! providerNode ) {
6440
- mibRequest = new MibRequest ({
6538
+ mibRequests[i] = new MibRequest ({
6441
6539
  operation: pdu.pduType,
6442
6540
  oid: requestVarbind.oid
6443
6541
  });
6444
- handler = function getNsiHandler (mibRequestForNsi) {
6445
- mibRequestForNsi.done ({
6542
+ mibRequests[i].error = {
6446
6543
  errorStatus: ErrorStatus.NoError,
6447
6544
  errorIndex: 0,
6448
6545
  type: ObjectType.NoSuchInstance,
6449
6546
  value: null
6450
- });
6451
6547
  };
6452
6548
  } else {
6453
- mibRequest = new MibRequest ({
6549
+ mibRequests[i] = new MibRequest ({
6454
6550
  operation: pdu.pduType,
6455
6551
  providerNode: providerNode,
6456
6552
  instanceNode: instanceNode,
6457
- oid: requestVarbind.oid
6553
+ oid: requestVarbind.oid,
6458
6554
  });
6459
- if ( pdu.pduType == AgentXPduType.TestSet ) {
6460
- mibRequest.setType = requestVarbind.type;
6461
- mibRequest.setValue = requestVarbind.value;
6555
+ mibRequests[i].handler = providerNode.provider.handler;
6556
+ if ( ! me.isAllowed(pdu.pduType, mibRequests[i].providerNode?.provider, mibRequests[i].instanceNode) ) {
6557
+ mibRequests[i].error = {
6558
+ errorStatus: ErrorStatus.NoAccess,
6559
+ errorIndex: i + 1,
6560
+ type: ObjectType.Null,
6561
+ value: null
6562
+ };
6563
+ }
6564
+ if ( isSetRequest ) {
6565
+ mibRequests[i].setType = instanceNode.valueType;
6566
+ mibRequests[i].setValue = requestVarbind.requestValue ?? requestVarbind.value;
6567
+ mibRequests[i].requestIndex = i + 1;
6568
+ try {
6569
+ mibRequests[i].setValue =
6570
+ ObjectTypeUtil.castSetValue (mibRequests[i].setType, mibRequests[i].setValue);
6571
+
6572
+ if ( ! mibRequests[i].instanceNode.validateValue (mibRequests[i].setType, mibRequests[i].setValue) ) {
6573
+ mibRequests[i].error = {
6574
+ errorStatus: ErrorStatus.BadValue,
6575
+ errorIndex: i + 1,
6576
+ type: mibRequests[i].setType,
6577
+ value: mibRequests[i].setValue,
6578
+ };
6579
+ }
6580
+ } catch (e) {
6581
+ debug('Invalid value for type', e, mibRequests[i]);
6582
+ mibRequests[i].error = {
6583
+ errorStatus: ErrorStatus.WrongType,
6584
+ errorIndex: i + 1,
6585
+ type: mibRequests[i].setType,
6586
+ value: mibRequests[i].setValue,
6587
+ };
6588
+ }
6462
6589
  }
6463
- handler = providerNode.provider.handler;
6464
6590
  }
6465
6591
  }
6466
6592
 
6467
6593
  (function (savedIndex) {
6594
+ const mibRequest = mibRequests[savedIndex];
6595
+ const requestVarbind = requestVarbinds[savedIndex];
6468
6596
  mibRequest.done = function (error) {
6597
+ mibRequest.error = error ?? { errorStatus: ErrorStatus.NoError };
6469
6598
  let responseVarbind;
6470
6599
  if ( error ) {
6471
6600
  responseVarbind = {
6472
6601
  oid: mibRequest.oid,
6473
6602
  type: error.type || ObjectType.Null,
6474
- value: error.value || null
6603
+ value: error.value ?? null
6475
6604
  };
6476
- if ( (typeof responsePdu.errorStatus == "undefined" || responsePdu.errorStatus == ErrorStatus.NoError) && error.errorStatus != ErrorStatus.NoError ) {
6477
- responsePdu.error = error.errorStatus;
6478
- responsePdu.index = savedIndex + 1;
6479
- }
6605
+ error.errorIndex = savedIndex + 1;
6606
+ firstVarbindError = firstVarbindError ?? error;
6480
6607
  if ( error.errorStatus != ErrorStatus.NoError ) {
6481
6608
  responseVarbind.errorStatus = error.errorStatus;
6482
6609
  }
6483
6610
  } else {
6484
- if ( pdu.pduType == AgentXPduType.TestSet ) {
6611
+ if ( isTestSet ) {
6485
6612
  // more tests?
6486
6613
  } else if ( pdu.pduType == AgentXPduType.CommitSet ) {
6487
6614
  me.setTransactions[pdu.transactionID].originalValue = mibRequest.instanceNode.value;
@@ -6501,23 +6628,54 @@ Subagent.prototype.request = function (pdu, requestVarbinds) {
6501
6628
  value: mibRequest.instanceNode.value
6502
6629
  };
6503
6630
  }
6504
- responseVarbinds[savedIndex] = responseVarbind;
6505
- if ( ++varbindsCompleted == varbindsLength) {
6506
- if ( pdu.pduType == AgentXPduType.TestSet || pdu.pduType == AgentXPduType.CommitSet
6507
- || pdu.pduType == AgentXPduType.UndoSet) {
6631
+ responseVarbinds[savedIndex] = mibRequest.response = responseVarbind;
6632
+ if ( ++varbindsCompleted == varbindsLength ) {
6633
+ if ( isSetRequest ) {
6634
+ responsePdu.error = firstVarbindError?.errorStatus ?? 0;
6635
+ responsePdu.index = firstVarbindError?.errorIndex ?? 0;
6508
6636
  me.sendResponse.call (me, responsePdu);
6509
6637
  } else {
6510
6638
  me.sendResponse.call (me, responsePdu, responseVarbinds);
6511
6639
  }
6512
6640
  }
6513
6641
  };
6642
+ if ( isTestSet )
6643
+ mibRequests[i].testSet = true;
6644
+ else if ( pdu.pduType == AgentXPduType.CommitSet )
6645
+ mibRequests[i].commitSet = true;
6646
+ if ( mibRequest.error )
6647
+ mibRequest.done(mibRequest.error);
6514
6648
  })(i);
6515
- if ( handler ) {
6516
- handler (mibRequest);
6517
- } else {
6518
- mibRequest.done ();
6649
+ }
6650
+ if ( isSetRequest && this.bulkSetHandler ) {
6651
+ const errorStatus = this.bulkSetHandler( mibRequests, this.mib, isTestSet ) ?? ErrorStatus.NoError;
6652
+ if ( errorStatus !== ErrorStatus.NoError ) {
6653
+ for ( const mibRequest of mibRequests ) {
6654
+ if ( !mibRequest.response ) {
6655
+ mibRequest.done ({
6656
+ errorStatus,
6657
+ type: ObjectType.Null,
6658
+ value: null
6659
+ });
6660
+ }
6661
+ }
6662
+ return;
6519
6663
  }
6520
6664
  }
6665
+ for ( let i = 0; i < requestVarbinds.length; i++ ) {
6666
+ if ( !mibRequests[i].response ) {
6667
+ const handler = mibRequests[i].handler;
6668
+ if ( handler ) {
6669
+ handler (mibRequests[i]);
6670
+ } else {
6671
+ mibRequests[i].done ();
6672
+ }
6673
+ }
6674
+ }
6675
+ };
6676
+
6677
+ Subagent.prototype.setBulkSetHandler = function setBulkSetHandler(cb) {
6678
+ this.bulkSetHandler = cb;
6521
6679
  };
6522
6680
 
6523
6681
  Subagent.prototype.addGetNextVarbind = function (targetVarbinds, startOid) {
@@ -6680,6 +6838,7 @@ exports.ErrorStatus = ErrorStatus;
6680
6838
  exports.TrapType = TrapType;
6681
6839
  exports.ObjectType = ObjectType;
6682
6840
  exports.PduType = PduType;
6841
+ exports.AgentXPdu = AgentXPdu;
6683
6842
  exports.AgentXPduType = AgentXPduType;
6684
6843
  exports.MibProviderType = MibProviderType;
6685
6844
  exports.SecurityLevel = SecurityLevel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "net-snmp",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "description": "JavaScript implementation of the Simple Network Management Protocol (SNMP)",
5
5
  "author": "Mark Abrahams <mark@abrahams.co.nz>",
6
6
  "license": "MIT",
@@ -357,7 +357,7 @@ describe('Subagent', function() {
357
357
  subagent.getMib().setScalarValue('testScalar', 100);
358
358
  });
359
359
 
360
- xit('manages set transactions correctly', function() {
360
+ it('manages set transactions correctly', function() {
361
361
  // Create proper AgentXPdu objects using createFromVariables
362
362
  const testSetPdu = snmp.AgentXPdu.createFromVariables({
363
363
  pduType: snmp.AgentXPduType.TestSet,
@@ -383,7 +383,7 @@ describe('Subagent', function() {
383
383
  assert(!subagent.setTransactions[123]);
384
384
  });
385
385
 
386
- xit('handles unexpected transaction IDs', function() {
386
+ it('handles unexpected transaction IDs', function() {
387
387
  const commitSetPdu = snmp.AgentXPdu.createFromVariables({
388
388
  pduType: snmp.AgentXPduType.CommitSet,
389
389
  sessionID: subagent.sessionID,