/*
* Copyright 2011-16 Fraunhofer ISE
*
* This file is part of OpenMUC.
* For more information visit http://www.openmuc.org
*
* OpenMUC is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenMUC 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenMUC. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openmuc.framework.driver.snmp.implementation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.openmuc.framework.config.ArgumentSyntaxException;
import org.openmuc.framework.config.ChannelScanInfo;
import org.openmuc.framework.data.ByteArrayValue;
import org.openmuc.framework.data.Flag;
import org.openmuc.framework.data.Record;
import org.openmuc.framework.driver.spi.ChannelRecordContainer;
import org.openmuc.framework.driver.spi.ChannelValueContainer;
import org.openmuc.framework.driver.spi.Connection;
import org.openmuc.framework.driver.spi.ConnectionException;
import org.openmuc.framework.driver.spi.RecordsReceivedListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.AbstractTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.MPv3;
import org.snmp4j.security.SecurityModels;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.security.USM;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
/**
*
* Super class for defining SNMP enabled devices.
*
* @author Mehran Shakeri
*
*/
public abstract class SnmpDevice implements Connection {
private final static Logger logger = LoggerFactory.getLogger(SnmpDevice.class);
public enum SNMPVersion {
V1,
V2c,
V3
};
protected Address targetAddress;
protected Snmp snmp;
protected USM usm;
protected int timeout = 3000; // in milliseconds
protected int retries = 3;
protected String authenticationPassphrase;
protected AbstractTarget target;
protected List<SnmpDiscoveryListener> listeners = new ArrayList<>();
public static final Map<String, String> ScanOIDs = new HashMap<>();
static {
// some general OIDs that are valid in almost every MIB
ScanOIDs.put("Device name: ", "1.3.6.1.2.1.1.5.0");
ScanOIDs.put("Description: ", "1.3.6.1.2.1.1.1.0");
ScanOIDs.put("Location: ", "1.3.6.1.2.1.1.6.0");
};
/**
* snmp constructor takes primary parameters in order to create snmp object. this implementation uses UDP protocol
*
* @param address
* Contains ip and port. accepted string "X.X.X.X/portNo"
* @param authenticationPassphrase
* the authentication pass phrase. If not <code>null</code>, <code>authenticationProtocol</code> must
* also be not <code>null</code>. RFC3414 ยง11.2 requires pass phrases to have a minimum length of 8
* bytes. If the length of <code>authenticationPassphrase</code> is less than 8 bytes an
* <code>IllegalArgumentException</code> is thrown. [required by snmp4j library]
*
* @throws ConnectionException
* thrown if SNMP listen or initialization failed
* @throws ArgumentSyntaxException
* thrown if Device address foramt is wrong
*/
public SnmpDevice(String address, String authenticationPassphrase)
throws ConnectionException, ArgumentSyntaxException {
// start snmp compatible with all versions
try {
snmp = new Snmp(new DefaultUdpTransportMapping());
} catch (IOException e) {
throw new ConnectionException("SNMP initialization failed! \n" + e.getMessage());
}
usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
try {
snmp.listen();
} catch (IOException e) {
throw new ConnectionException("SNMP listen failed! \n" + e.getMessage());
}
// set address
try {
targetAddress = GenericAddress.parse(address);
} catch (IllegalArgumentException e) {
throw new ArgumentSyntaxException("Device address foramt is wrong! (eg. 1.1.1.1/1)");
}
this.authenticationPassphrase = authenticationPassphrase;
}
/**
* Default constructor useful for scanner
*/
public SnmpDevice() {
}
/**
* set target parameters. Implementations are different in SNMP v1, v2c and v3
*/
abstract void setTarget();
/**
* Receives a list of all OIDs in string format, creates PDU and sends GET request to defined target. This method is
* a blocking method. It waits for response.
*
* @param OIDs
* list of OIDs that should be read from target
* @return Map<String, String> returns a Map of OID as Key and received value corresponding to that OID from
* the target as Value
*
* @throws SnmpTimeoutException
* thrown if Target doesn't responses
* @throws ConnectionException
* thrown if SNMP get request fails
*/
public Map<String, String> getRequestsList(List<String> OIDs) throws SnmpTimeoutException, ConnectionException {
Map<String, String> result = new HashMap<>();
// set PDU
PDU pdu = new PDU();
pdu.setType(PDU.GET);
for (String oid : OIDs) {
pdu.add(new VariableBinding(new OID(oid)));
}
// send GET request
ResponseEvent response;
try {
response = snmp.send(pdu, target);
PDU responsePDU = response.getResponse();
@SuppressWarnings("rawtypes")
Vector vbs = responsePDU.getVariableBindings();
for (int i = 0; i < vbs.size(); i++) {
VariableBinding vb = (VariableBinding) vbs.get(i);
result.put(vb.getOid().toString(), vb.getVariable().toString());
}
} catch (IOException e) {
throw new ConnectionException("SNMP get request failed! " + e.getMessage());
} catch (NullPointerException e) {
throw new SnmpTimeoutException("Timeout: Target doesn't respond!");
}
return result;
}
/**
* Receives one single OID in string format, creates PDU and sends GET request to defined target. This method is a
* blocking method. It waits for response.
*
* @param OID
* OID that should be read from target
* @return String containing read value
*
* @throws SnmpTimeoutException
* thrown if Target doesn't responses
* @throws ConnectionException
* thrown if SNMP get request failsn
*/
public String getSingleRequests(String OID) throws SnmpTimeoutException, ConnectionException {
String result = null;
// set PDU
PDU pdu = new PDU();
pdu.setType(PDU.GET);
pdu.add(new VariableBinding(new OID(OID)));
// send GET request
ResponseEvent response;
try {
response = snmp.send(pdu, target);
PDU responsePDU = response.getResponse();
@SuppressWarnings("rawtypes")
Vector vbs = responsePDU.getVariableBindings();
result = ((VariableBinding) vbs.get(0)).getVariable().toString();
} catch (IOException e) {
throw new ConnectionException("SNMP get request failed! " + e.getMessage());
} catch (NullPointerException e) {
throw new SnmpTimeoutException("Timeout: Target doesn't respond!");
}
return result;
}
public String getDeviceAddress() {
return targetAddress.toString();
}
public synchronized void addEventListener(SnmpDiscoveryListener listener) {
listeners.add(listener);
}
public synchronized void removeEventListener(SnmpDiscoveryListener listener) {
listeners.remove(listener);
}
/**
* This method will call all listeners for given new device
*
* @param address
* address of device
* @param version
* version of snmp that this device support
* @param description
* other extra information which can be useful
*
*/
protected synchronized void NotifyForNewDevice(Address address, SNMPVersion version, String description) {
SnmpDiscoveryEvent event = new SnmpDiscoveryEvent(this, address, version, description);
@SuppressWarnings("rawtypes")
Iterator i = listeners.iterator();
while (i.hasNext()) {
((SnmpDiscoveryListener) i.next()).onNewDeviceFound(event);
}
}
/**
* Calculate and return next broadcast address. (eg. if ip=1.2.3.x, returns 1.2.4.255)
*
* @param ip
* IP
* @return String the next broadcast address as String
*/
public static String getNextBroadcastIPV4Address(String ip) {
String[] nums = ip.split("\\.");
int i = (Integer.parseInt(nums[0]) << 24 | Integer.parseInt(nums[2]) << 8 | Integer.parseInt(nums[1]) << 16
| Integer.parseInt(nums[3])) + 256;
return String.format("%d.%d.%d.%d", i >>> 24 & 0xFF, i >> 16 & 0xFF, i >> 8 & 0xFF, 255);
}
/**
* Helper function in order to parse response vector to map structure
*
* @param responseVector
* response vector
* @return HashMap<String, String>
*/
public static HashMap<String, String> parseResponseVectorToHashMap(Vector<VariableBinding> responseVector) {
HashMap<String, String> map = new HashMap<>();
for (VariableBinding elem : responseVector) {
map.put(elem.getOid().toString(), elem.getVariable().toString());
}
return map;
}
protected static String scannerMakeDescriptionString(HashMap<String, String> scannerResult) {
StringBuilder desc = new StringBuilder();
for (String key : ScanOIDs.keySet()) {
desc.append('[')
.append(key)
.append('(')
.append(ScanOIDs.get(key))
.append(")=")
.append(scannerResult.get(ScanOIDs.get(key)))
.append("] ");
}
return desc.toString();
}
/**
* Returns respective SNMPVersion enum value based on given SnmpConstant version value
*
* @param version
* the version as int
* @return SNMPVersion or null if given value is not valid
*/
protected static SNMPVersion getSnmpVersionFromSnmpConstantsValue(int version) {
switch (version) {
case 0:
return SNMPVersion.V1;
case 1:
return SNMPVersion.V2c;
case 3:
return SNMPVersion.V3;
}
return null;
}
@Override
public void disconnect() {
}
/**
* At least device address and channel address must be specified in the container.<br>
* <br>
* containers.deviceAddress = device address (eg. 1.1.1.1/161) <br>
* containers.channelAddress = OID (eg. 1.3.6.1.2.1.1.0)
*
*/
@Override
public Object read(List<ChannelRecordContainer> containers, Object containerListHandle, String samplingGroup)
throws ConnectionException {
return readChannelGroup(containers, timeout);
}
@Override
public Object write(List<ChannelValueContainer> containers, Object containerListHandle)
throws UnsupportedOperationException, ConnectionException {
// TODO snmp set request will be implemented here
throw new UnsupportedOperationException();
}
/**
* Read all the channels of the device at once.
*
* @param device
* @param containers
* @param timeout
* @return Object
* @throws ConnectionException
*/
private Object readChannelGroup(List<ChannelRecordContainer> containers, int timeout) throws ConnectionException {
new Date().getTime();
List<String> oids = new ArrayList<>();
for (ChannelRecordContainer container : containers) {
if (getDeviceAddress().equalsIgnoreCase(container.getChannel().getDeviceAddress())) {
oids.add(container.getChannelAddress());
}
}
Map<String, String> values;
try {
values = getRequestsList(oids);
long receiveTime = System.currentTimeMillis();
for (ChannelRecordContainer container : containers) {
// make sure the value exists for corresponding channel
if (values.get(container.getChannelAddress()) != null) {
logger.debug("{}: value = '{}'", container.getChannelAddress(),
values.get(container.getChannelAddress()));
container.setRecord(new Record(
new ByteArrayValue(values.get(container.getChannelAddress()).getBytes()), receiveTime));
}
}
} catch (SnmpTimeoutException e) {
for (ChannelRecordContainer container : containers) {
container.setRecord(new Record(Flag.TIMEOUT));
}
}
return null;
}
@Override
public void startListening(List<ChannelRecordContainer> containers, RecordsReceivedListener listener)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public List<ChannelScanInfo> scanForChannels(String settings)
throws UnsupportedOperationException, ConnectionException {
throw new UnsupportedOperationException();
}
}