/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.snmp.internal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.openhab.binding.snmp.SnmpBindingProvider; import org.openhab.core.binding.BindingConfig; import org.openhab.core.items.Item; import org.openhab.core.library.items.NumberItem; import org.openhab.core.library.items.StringItem; import org.openhab.core.library.items.SwitchItem; import org.openhab.core.library.types.StringType; import org.openhab.core.transform.TransformationException; import org.openhab.core.transform.TransformationHelper; import org.openhab.core.transform.TransformationService; import org.openhab.core.types.Command; import org.openhab.core.types.TypeParser; import org.openhab.model.item.binding.AbstractGenericBindingProvider; import org.openhab.model.item.binding.BindingConfigParseException; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.mp.SnmpConstants; import org.snmp4j.smi.Address; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; /** * <p> * This class can parse information from the generic binding format and provides * SNMP binding information from it. It registers as a * {@link SnmpBindingProvider} service as well. * </p> * * <p> * Here are some examples for valid binding configuration strings: * <ul> * <li> * <code>{ snmp="<[192.168.2.253:public:.1.3.6.1.2.1.2.2.1.10.10:10000]" }</code> * - receives status updates for the given OID</li> * <li> * <li> * <code>{ snmp="<[192.168.2.253:public:.1.3.6.1.2.1.2.2.1.10.10:10000:MAP(abc.map)]" }</code> * - receives status updates for the given OID and transforms the result with the MAP file</li> * <li> * <code>{ snmp="<[192.168.2.253:public:.1.3.6.1.2.1.2.2.1.10.10:0]" }</code> - * receives trap updates for the given OID</li> * <li> * <code>{snmp=">[OFF:192.168.2.252:private:.1.3.6.1.4.1.4526.11.16.1.1.1.3.1.2:2]" }</code> * - sets the command OFF to set an integer value 2 to the given OID * </ul> * </p> * * The given config strings are only valid for {@link StringItem}s. * * @author Thomas.Eichstaedt-Engelen * @author Chris Jackson - modified binding to support polling SNMP OIDs (SNMP * GET) and setting values (SNMP SET). * @author Jan N. Klug - modified binding to change protocol version * @since 0.9.0 */ public class SnmpGenericBindingProvider extends AbstractGenericBindingProvider implements SnmpBindingProvider { static final Logger logger = LoggerFactory.getLogger(SnmpGenericBindingProvider.class); /** * Artificial command for the snmp-in configuration */ protected static final Command IN_BINDING_KEY = StringType.valueOf("IN_BINDING"); /** {@link Pattern} which matches a binding configuration part */ private static final Pattern BASE_CONFIG_PATTERN = Pattern.compile("([<|>|\\*]\\[.*?\\])*"); /** {@link Pattern} which matches an In-Binding */ private static final Pattern IN_BINDING_PATTERN = Pattern .compile("<\\[([0-9.a-zA-Z/]+):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)\\]"); private static final Pattern IN_BINDING_PATTERN_TRANSFORM = Pattern .compile("<\\[([0-9.a-zA-Z/]+):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+):(.*)?\\]"); private static final Pattern IN_BINDING_PATTERN_VERSION = Pattern .compile("<\\[([0-9.a-zA-Z/]+):(v1|v2c|v3):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)\\]"); private static final Pattern IN_BINDING_PATTERN_VERSION_TRANSFORM = Pattern .compile("<\\[([0-9.a-zA-Z/]+):(v1|v2c|v3):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+):(.*)?\\]"); /** {@link Pattern} which matches an Out-Binding */ private static final Pattern OUT_BINDING_PATTERN = Pattern .compile(">\\[([0-9.a-zA-Z]+):([0-9.a-zA-Z/]+):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)\\]"); private static final Pattern OUT_BINDING_PATTERN_VERSION = Pattern .compile(">\\[([0-9.a-zA-Z]+):([0-9.a-zA-Z/]+):(v1|v2c|v3):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)\\]"); /** * {@inheritDoc} */ @Override public String getBindingType() { return "snmp"; } /** * {@inheritDoc} */ @Override public void validateItemType(Item item, String bindingConfig) throws BindingConfigParseException { if (!(item instanceof StringItem || item instanceof NumberItem || item instanceof SwitchItem)) { throw new BindingConfigParseException("Item '" + item.getName() + "' is of type '" + item.getClass().getSimpleName() + "', only StringItems, NumberItems and SwitchItems are allowed - please check your *.items configuration"); } } /** * {@inheritDoc} */ @Override public void processBindingConfiguration(String context, Item item, String bindingConfig) throws BindingConfigParseException { super.processBindingConfiguration(context, item, bindingConfig); if (bindingConfig != null) { SnmpBindingConfig newConfig = new SnmpBindingConfig(); Matcher matcher = BASE_CONFIG_PATTERN.matcher(bindingConfig); if (!matcher.matches()) { throw new BindingConfigParseException( "bindingConfig '" + bindingConfig + "' doesn't contain a valid binding configuration"); } matcher.reset(); while (matcher.find()) { String bindingConfigPart = matcher.group(1); if (StringUtils.isNotBlank(bindingConfigPart)) { parseBindingConfig(newConfig, item, bindingConfigPart); } } addBindingConfig(item, newConfig); } else { logger.warn("bindingConfig is NULL (item={}) -> processing bindingConfig aborted!", item); } } /** * Parses a SNMP-OUT configuration by using the regular expression * <code>([0-9.a-zA-Z/]+):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)</code>. * Where the groups should contain the following content: * <ul> * <li>Command</li> * <li>url: ip[/port], port is optional, default: 161</li> * <li>[Optional]Version: v1, v2c, v3</li> * <li>SNMP community</li> * <li>OID</li> * <li>Value</li> * </ul> * * Parses a SNMP-IN configuration by using the regular expression * <code>([0-9.a-zA-Z/]+):([0-9.a-zA-Z]+):([0-9.a-zA-Z]+):([0-9]+)</code>. * Where the groups should contain the following content: * <ul> * <li>url: ip[/port], port is optional, default: 161</li> * <li>[Optional]Version: v1, v2c, v3</li> * <li>SNMP community</li> * <li>OID</li> * <li>Refresh interval (ms)</li> * <li>[Optional]transformation rule</li> * </ul> * * Setting refresh interval to 0 will only receive SNMP traps * * @param config * - the Configuration that needs to be updated with the parsing * results * @param item * - the Item that this configuration is intended for * @param bindingConfig * - the configuration string that will be parsed * @throws BindingConfigParseException */ private void parseBindingConfig(SnmpBindingConfig config, Item item, String bindingConfig) throws BindingConfigParseException { config.itemType = item.getClass(); if (bindingConfig != null) { // try in without version first Matcher inMatcher = IN_BINDING_PATTERN.matcher(bindingConfig); if (!inMatcher.matches()) { inMatcher = IN_BINDING_PATTERN_TRANSFORM.matcher(bindingConfig); } if (inMatcher.matches()) { SnmpBindingConfigElement newElement = new SnmpBindingConfigElement(); newElement.address = parseAddress(inMatcher.group(1).toString()); newElement.snmpVersion = SnmpConstants.version1; newElement.community = new OctetString(inMatcher.group(2).toString()); newElement.oid = new OID(inMatcher.group(3).toString()); newElement.refreshInterval = Integer.valueOf(inMatcher.group(4)).intValue(); if (inMatcher.groupCount() == 5) { newElement.setTransformationRule(inMatcher.group(5)); } config.put(IN_BINDING_KEY, newElement); } else { // not matched, try with version inMatcher = IN_BINDING_PATTERN_VERSION.matcher(bindingConfig); if (!inMatcher.matches()) { inMatcher = IN_BINDING_PATTERN_VERSION_TRANSFORM.matcher(bindingConfig); } if (inMatcher.matches()) { SnmpBindingConfigElement newElement = new SnmpBindingConfigElement(); newElement.address = parseAddress(inMatcher.group(1).toString()); String version = inMatcher.group(2).toString(); if (version.equals("v3")) { newElement.snmpVersion = SnmpConstants.version3; } else if (version.equals("v2c")) { newElement.snmpVersion = SnmpConstants.version2c; } else { newElement.snmpVersion = SnmpConstants.version1; } newElement.community = new OctetString(inMatcher.group(3).toString()); newElement.oid = new OID(inMatcher.group(4).toString()); newElement.refreshInterval = Integer.valueOf(inMatcher.group(5)).intValue(); if (inMatcher.groupCount() == 6) { newElement.setTransformationRule(inMatcher.group(6)); } config.put(IN_BINDING_KEY, newElement); } } Matcher outMatcher = OUT_BINDING_PATTERN.matcher(bindingConfig); if (outMatcher.matches()) { SnmpBindingConfigElement newElement = new SnmpBindingConfigElement(); String commandAsString = outMatcher.group(1).toString(); newElement.address = parseAddress(outMatcher.group(2).toString()); newElement.snmpVersion = SnmpConstants.version1; newElement.community = new OctetString(outMatcher.group(3).toString()); newElement.oid = new OID(outMatcher.group(4).toString()); // Only Integer commands accepted at this time. newElement.value = new Integer32(Integer.parseInt(outMatcher.group(5).toString())); Command command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandAsString); if (command == null) { logger.error("SNMP can't resolve command {} for item {}", commandAsString, item); } else { config.put(command, newElement); } } else { outMatcher = OUT_BINDING_PATTERN_VERSION.matcher(bindingConfig); if (outMatcher.matches()) { SnmpBindingConfigElement newElement = new SnmpBindingConfigElement(); String commandAsString = outMatcher.group(1).toString(); newElement.address = parseAddress(outMatcher.group(2).toString()); String version = inMatcher.group(3).toString(); if (version.equals("v3")) { newElement.snmpVersion = SnmpConstants.version3; } else if (version.equals("v2c")) { newElement.snmpVersion = SnmpConstants.version2c; } else { newElement.snmpVersion = SnmpConstants.version1; } newElement.community = new OctetString(outMatcher.group(4).toString()); newElement.oid = new OID(outMatcher.group(5).toString()); // Only Integer commands accepted at this time. newElement.value = new Integer32(Integer.parseInt(outMatcher.group(6).toString())); Command command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandAsString); if (command == null) { logger.error("SNMP can't resolve command {} for item {}", commandAsString, item); } else { config.put(command, newElement); } } } // have we found any matches? if (!outMatcher.matches() && !inMatcher.matches()) { throw new BindingConfigParseException( getBindingType() + " binding configuration must consist of four/five/six [config=" + inMatcher + "] or five/six parts [config=" + outMatcher + "]"); } } else { return; } } private Address parseAddress(String s) throws BindingConfigParseException { String addressString = s.contains("/") ? s : s + "/161"; Address address = GenericAddress.parse("udp:" + addressString); if (address == null) { throw new BindingConfigParseException(getBindingType() + " binding configuration address is invalid: " + s); } return address; } /** * @{inheritDoc */ @Override public List<String> getInBindingItemNames() { List<String> inBindings = new ArrayList<String>(); for (String itemName : bindingConfigs.keySet()) { SnmpBindingConfig snmpConfig = (SnmpBindingConfig) bindingConfigs.get(itemName); if (snmpConfig.containsKey(IN_BINDING_KEY)) { inBindings.add(itemName); } } return inBindings; } /** * @{inheritDoc */ @Override public Class<? extends Item> getItemType(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.itemType : null; } /** * @{inheritDoc */ @Override public OID getOID(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(IN_BINDING_KEY).oid : new OID(""); } /** * @{inheritDoc */ @Override public OID getOID(String itemName, Command command) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(command).oid : new OID(""); } /** * @{inheritDoc */ @Override public int getSnmpVersion(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(IN_BINDING_KEY).snmpVersion : SnmpConstants.version1; } /** * @{inheritDoc */ @Override public int getSnmpVersion(String itemName, Command command) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(command).snmpVersion : SnmpConstants.version1; } /** * @{inheritDoc */ @Override public Address getAddress(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(IN_BINDING_KEY).address : null; } /** * @{inheritDoc */ @Override public Address getAddress(String itemName, Command command) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(command).address : null; } /** * @{inheritDoc */ @Override public Integer32 getValue(String itemName, Command command) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(command).value : null; } /** * @{inheritDoc */ @Override public OctetString getCommunity(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(IN_BINDING_KEY).community : new OctetString(); } /** * @{inheritDoc */ @Override public OctetString getCommunity(String itemName, Command command) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(command).community : new OctetString(); } /** * @{inheritDoc */ public TransformationService getTransformationService(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null ? config.get(IN_BINDING_KEY).transformationService : null; } /** * {@inheritDoc} */ @Override public int getRefreshInterval(String itemName) { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); return config != null && config.get(IN_BINDING_KEY) != null ? config.get(IN_BINDING_KEY).refreshInterval : 0; } static class SnmpBindingConfig extends HashMap<Command, SnmpBindingConfigElement> implements BindingConfig { private static final long serialVersionUID = 4697146075427676116L; Class<? extends Item> itemType; } /** * This is an internal data structure to store information from the binding * config strings and use it to answer the requests to the SNMP binding * provider. */ static class SnmpBindingConfigElement implements BindingConfig { public OID oid; public int refreshInterval; public OctetString community; public int snmpVersion; public Address address; public Integer32 value; public TransformationService transformationService; public String transformationName; public String transformationParam; @Override public String toString() { return "SnmpBindingConfigElement [address=" + address.toString() + ", oid=" + oid.toString() + ", refreshInterval=" + refreshInterval + ", community=" + community.toString() + "]"; } public boolean setTransformationRule(String rule) { int pos = rule.indexOf('('); if (pos == -1) { return false; } // Split the transformation rule transformationName = rule.substring(0, pos); transformationParam = rule.substring(pos + 1, rule.length() - 1); BundleContext context = SnmpActivator.getContext(); // Get the transformation service transformationService = TransformationHelper.getTransformationService(context, transformationName); if (transformationService == null) { logger.debug("No transformation service found for {}", transformationName); return false; } return true; } public String doTransformation(String value) throws TransformationException { if (transformationService == null) { return value; } return transformationService.transform(transformationParam, value); } } @Override public String doTransformation(String itemName, String value) throws TransformationException { SnmpBindingConfig config = (SnmpBindingConfig) bindingConfigs.get(itemName); if (config == null) { return value; } if (config.get(IN_BINDING_KEY) == null) { return value; } return config.get(IN_BINDING_KEY).doTransformation(value); } }