Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/main/java/org/metricshub/snmp/client/ISnmpClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*-
* ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
* SNMP Java Client
* ჻჻჻჻჻჻
* Copyright 2023 MetricsHub
* ჻჻჻჻჻჻
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

package org.metricshub.snmp.client;

import java.util.List;

/**
* Unified abstraction that both {@link SnmpClient} (live network) and
* {@link OfflineSnmpClient} (file‑backed) can implement.
*/
public interface ISnmpClient {

/**
* Performs SNMP get action for a single OID.
* @param oid A given OID.
* @return The corresponding object as a string.
* @throws Exception on error (e.g. no such OID).
*/
String get(String oid) throws Exception;

/**
* Performs SNMP getNext action for a single OID.
* @param oid A given OID.
* @return The corresponding object as a string.
* @throws Exception on error (e.g. no such OID).
*/
String getNext(String oid) throws Exception;


/**
* Reads an SNMP table.
*
* @param rootOID Root OID of the table (e.g. …7.1)
* @param selectColumns Numeric column indexes or "ID" for the row index
* @return Rows serialized with semicolons (one row per line)
*/
List<List<String>> table(String rootOID, String[] selectColumns) throws Exception;

/**
* Strips the leading dot from an OID string if it exists.
*
* @param s the OID string to process.
* @return the OID string without a leading dot.
*/
default String stripDot(String s) {
return s.startsWith(".") ? s.substring(1) : s;
}

/**
* Performs SNMP walk action starting from a given OID.
* @param oid A given OID.
* @return The corresponding object as a string.
* @throws Exception on error (e.g. no such OID).
*/
String walk(String oid) throws Exception;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Javadoc.

}
202 changes: 202 additions & 0 deletions src/main/java/org/metricshub/snmp/client/OfflineSnmpClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*-
* ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
* SNMP Java Client
* ჻჻჻჻჻჻
* Copyright 2023 MetricsHub
* ჻჻჻჻჻჻
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

package org.metricshub.snmp.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.stream.Stream;

/**
* An offline SNMP client that reads OID values from a snmp WALK dump file and executes
* SNMP operations (GET, GET_NEXT, TABLE, WALK) against those OIDs.
*/
public final class OfflineSnmpClient implements ISnmpClient {

/** Container for Snmp object's type and value. */
private static final class SnmpValue {
final String type;
final String value;
SnmpValue(String type, String value) {
this.type = type;
this.value = value;
}
}

// Maps the OIDs to their corresponding SnmpValue (type + value).
private final NavigableMap<String, SnmpValue> oidValues = new TreeMap<>();


/**
* Reads oid values from all .walk files in the specified directory.
* @param directory A given directory containing .walk files
* @throws IOException If an I/O error occurs reading from the directory or a file
*/
public OfflineSnmpClient(Path directory) throws IOException {
try (Stream<Path> paths = Files.list(directory)) {
paths.filter(path -> path.toString().endsWith(".walk"))
.forEach(file -> {
try (BufferedReader br = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (!line.contains("\t")) continue;

final String[] parts = line.split("\\t", 3);
if (parts.length < 3) continue;

final String oid = stripDot(parts[0]);
final String type = parts[1];
final String value = parts[2];
oidValues.put(oid, new SnmpValue(type, value));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}

@Override
public String get(String oid) throws Exception {
final String key = stripDot(oid);
final SnmpValue val = oidValues.get(key);
if (val == null) {
throw new Exception("(no-such-oid)");
}
return val.value;
}

@Override
public String getNext(String oid) {
final Map.Entry<String, SnmpValue> next = oidValues.higherEntry(stripDot(oid));
if (next == null) {
return "(end-of-mib-view)";
}
final String nextOid = next.getKey();
final SnmpValue snmpVal = next.getValue();
return nextOid + "\t" + snmpVal.type + "\t" + snmpVal.value;
}

@Override
public List<List<String>> table(String rootOID, String[] selectColumns) {
if (rootOID == null || rootOID.length() < 3) {
throw new IllegalArgumentException("Invalid SNMP Table OID: " + rootOID);
}
if (selectColumns == null || selectColumns.length < 1) {
throw new IllegalArgumentException("Invalid SNMP Table columns");
}

final String base = stripDot(rootOID);

// find first column under base
final Map.Entry<String, SnmpValue> firstEntry = oidValues.higherEntry(base);
if (firstEntry == null || !firstEntry.getKey().startsWith(base + ".")) {
return new ArrayList<>(); // empty table
}

final String firstKey = firstEntry.getKey(); // e.g. base + ".1.<idx>"
final int dotAfter = firstKey.indexOf('.', base.length() + 1);
if (dotAfter < 0) return new ArrayList<>();

final String firstColOid = firstKey.substring(0, dotAfter); // e.g. "…7.1"
final int colOidLen = firstColOid.length();

// collect row IDs
final List<String> ids = new ArrayList<>();
String cursor = firstColOid;
while (true) {
final Map.Entry<String, SnmpValue> next = oidValues.higherEntry(cursor);
if (next == null) break;
final String key = next.getKey();
if (!key.startsWith(firstColOid + ".")) {
break;
}
final String id = key.substring(colOidLen + 1);
ids.add(id);
cursor = key;
if (ids.size() > 10000) {
break;
}
}

// build result rows
final List<List<String>> rows = new ArrayList<>(ids.size());
for (String id : ids) {
final List<String> row = new ArrayList<>(selectColumns.length);
for (String col : selectColumns) {
if ("ID".equals(col)) {
row.add(id);
} else {
String lookupOid = base + "." + col + "." + id;
try {
row.add(get(lookupOid));
} catch (Exception e) {
row.add("");
}
}
}
rows.add(row);
}
return rows;
}

@Override
public String walk(String oid) {
final String base = stripDot(oid);
final StringBuilder walkResult = new StringBuilder();

// Find the first OID that is >= base
Map.Entry<String, SnmpValue> entry = oidValues.ceilingEntry(base);
if (entry == null) {
return ""; // Nothing at or after base
}

while (entry != null) {
final String nextOid = entry.getKey();
if (!nextOid.startsWith(base + ".") && !nextOid.equals(base)) {
break; // left the subtree
}

final SnmpValue snmpValue = entry.getValue();
walkResult.append(nextOid)
.append("\t")
.append(snmpValue.type)
.append("\t")
.append(snmpValue.value)
.append("\n");

entry = oidValues.higherEntry(nextOid);
}

return walkResult.toString();
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/metricshub/snmp/client/SnmpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import java.util.List;
import java.util.Set;

public class SnmpClient {
public class SnmpClient implements ISnmpClient {

// Default SNMP port number
public static final int SNMP_PORT = 161;
Expand Down Expand Up @@ -126,7 +126,7 @@
this.host = host;
this.port = port;
this.snmpVersion = version;
this.retryIntervals = retryIntervals;

Check warning on line 129 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / spotbugs

EI_EXPOSE_REP2

new org.metricshub.snmp.client.SnmpClient(String, int, int, int[], String, String, String, String, String, String, String, byte[]) may expose internal representation by storing an externally mutable object into SnmpClient.retryIntervals
Raw output
 This code stores a reference to an externally mutable object into the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.
this.community = community;
this.authType = authType;
this.authUsername = authUsername;
Expand All @@ -134,7 +134,7 @@
this.privacyType = privacyType;
this.privacyPassword = privacyPassword;
this.contextName = contextName;
this.contextEngineID = contextID;

Check warning on line 137 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / spotbugs

EI_EXPOSE_REP2

new org.metricshub.snmp.client.SnmpClient(String, int, int, int[], String, String, String, String, String, String, String, byte[]) may expose internal representation by storing an externally mutable object into SnmpClient.contextEngineID
Raw output
 This code stores a reference to an externally mutable object into the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.

// Properly create the SNMP context, based on these properties
initialize();
Expand All @@ -153,27 +153,27 @@
// In case of SNMP v3, check the authType and privType (if not empty)
if (version == SNMP_V3) {
if (authType != null) {
if (!authType.isEmpty()) {
if (! SNMP_AUTH_PROTOCOLS.contains(authType)) {
throw new IllegalArgumentException("Invalid authentication method '" + authType + "'." +
" (Valid options are: '" + SNMP_AUTH_MD5
+ "', '" + SNMP_AUTH_SHA
+ "', '" + SNMP_AUTH_SHA256
+ "', '" + SNMP_AUTH_SHA512
+ "', '" + SNMP_AUTH_SHA224
+ "', '" + SNMP_AUTH_SHA384
+ "', or empty)");
}

Check warning on line 166 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / PMD

Design CollapsibleIfStatements

This if statement could be combined with its parent
}

Check warning on line 167 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / PMD

Design CollapsibleIfStatements

This if statement could be combined with its parent
}

if (privacyType != null) {
if (!privacyType.isEmpty()) {
if (! SNMP_PRIVACY_PROTOCOLS.contains(privacyType)) {
throw new IllegalArgumentException(
"Invalid privacy method '" + privacyType + "'." + " (Valid options are:'" + SNMP_PRIVACY_DES
+ "', '" + SNMP_PRIVACY_AES + "', '" + SNMP_PRIVACY_AES192 + "', '" + SNMP_PRIVACY_AES256 + "', or empty)");
}

Check warning on line 176 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / PMD

Design CollapsibleIfStatements

This if statement could be combined with its parent
}

Check warning on line 177 in src/main/java/org/metricshub/snmp/client/SnmpClient.java

View workflow job for this annotation

GitHub Actions / PMD

Design CollapsibleIfStatements

This if statement could be combined with its parent
}
}
Expand Down
Loading
Loading