/*_########################################################################## _## _## Copyright (C) 2011 Kaito Yamada _## _########################################################################## */ package com.github.kaitoy.sneo.util; import java.net.InetAddress; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.snmp4j.asn1.BER; import org.snmp4j.log.LogAdapter; import org.snmp4j.log.LogFactory; import org.snmp4j.smi.AbstractVariable; import org.snmp4j.smi.AssignableFromInteger; import org.snmp4j.smi.AssignableFromLong; import org.snmp4j.smi.AssignableFromString; import org.snmp4j.smi.BitString; import org.snmp4j.smi.IpAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.Opaque; import org.snmp4j.smi.SMIConstants; import org.snmp4j.smi.TimeTicks; import org.snmp4j.smi.Variable; import org.snmp4j.smi.VariableBinding; import org.snmp4j.util.VariableTextFormat; import static org.snmp4j.smi.SMIConstants.SYNTAX_INTEGER; import static org.snmp4j.smi.SMIConstants.SYNTAX_OCTET_STRING; import static org.snmp4j.smi.SMIConstants.SYNTAX_NULL; import static org.snmp4j.smi.SMIConstants.SYNTAX_OBJECT_IDENTIFIER; import static org.snmp4j.smi.SMIConstants.SYNTAX_IPADDRESS; import static org.snmp4j.smi.SMIConstants.SYNTAX_COUNTER32; import static org.snmp4j.smi.SMIConstants.SYNTAX_GAUGE32; import static org.snmp4j.smi.SMIConstants.SYNTAX_TIMETICKS; import static org.snmp4j.smi.SMIConstants.SYNTAX_OPAQUE; import static org.snmp4j.smi.SMIConstants.SYNTAX_COUNTER64; import static org.snmp4j.smi.SMIConstants.EXCEPTION_NO_SUCH_OBJECT; import static org.snmp4j.smi.SMIConstants.EXCEPTION_NO_SUCH_INSTANCE; import static org.snmp4j.smi.SMIConstants.EXCEPTION_END_OF_MIB_VIEW; /** * The <code>ColonSeparatedOidTypeValueVariableTextFormat</code> implements a simple textual * representation for SNMP variables based on their type only. * No MIB information is used (can be used). * * @author Kaito Yamada * @version 1.00 * @since 1.00 */ public class NetSnmpVariableTextFormat implements SneoVariableTextFormat { private static final LogAdapter logger = LogFactory.getLogger(VariableTextFormat.class); private static final String OID_TYPE_SEPARATOR = " = "; private static final String TYPE_VALUE_SEPARATOR = ": "; private static final char HEX_SEPARATOR = ' '; private static final char NETWORK_ADDRESS_SEPARATOR = ':'; private static final Map<String, Integer> SYNTAX_OF_TYPE_NAME = new HashMap<String, Integer>() ; private static final Map<Integer, String> TYPE_NAME_OF_SYNTAX = new HashMap<Integer, String>() ; private static final String REGEX_REGULAR_PATTERN; private static final String REGEX_EMPTY_STRING_PATTERN; private static final String REGEX_STRING_HEX_CONTINUATION_PATTERN; private static final String REGEX_BITS_CONTINUATION_PATTERN; private static final String REGEX_END_OF_MIB_VIEW_PATTERN; private static final String REGEX_NO_SUCH_INSTANCE_PATTERN; private static final String REGEX_NO_SUCH_OBJECT_PATTERN; private static final String REGEX_NULL_PATTERN; private static final int STRING_HEX_LENGTH_PER_LINE = 16 * 3; // ([\dA-F]{2}[ ]){16} private static final String STRING_HEX_LINE_SEPARATOR = System.getProperty("line.Separator", "\n"); static { SYNTAX_OF_TYPE_NAME.put("INTEGER", SYNTAX_INTEGER); SYNTAX_OF_TYPE_NAME.put("STRING", SYNTAX_OCTET_STRING); SYNTAX_OF_TYPE_NAME.put("BITS", (int)BER.BITSTRING); SYNTAX_OF_TYPE_NAME.put("Hex-STRING", SYNTAX_OCTET_STRING); SYNTAX_OF_TYPE_NAME.put("Network Address", SYNTAX_IPADDRESS); SYNTAX_OF_TYPE_NAME.put("OID", SYNTAX_OBJECT_IDENTIFIER); SYNTAX_OF_TYPE_NAME.put("Timeticks", SYNTAX_TIMETICKS); SYNTAX_OF_TYPE_NAME.put("Counter32", SYNTAX_COUNTER32); SYNTAX_OF_TYPE_NAME.put("Counter64", SYNTAX_COUNTER64); SYNTAX_OF_TYPE_NAME.put("No more variables left in this MIB View (It is past the end of the MIB tree)", EXCEPTION_END_OF_MIB_VIEW); SYNTAX_OF_TYPE_NAME.put("Gauge32", SYNTAX_GAUGE32); SYNTAX_OF_TYPE_NAME.put("Unsigned32", SYNTAX_GAUGE32); SYNTAX_OF_TYPE_NAME.put("IpAddress", SYNTAX_IPADDRESS); SYNTAX_OF_TYPE_NAME.put("No Such Instance currently exists at this OID", EXCEPTION_NO_SUCH_INSTANCE); SYNTAX_OF_TYPE_NAME.put("No Such Object available on this agent at this OID", EXCEPTION_NO_SUCH_OBJECT); SYNTAX_OF_TYPE_NAME.put("NULL", SYNTAX_NULL); SYNTAX_OF_TYPE_NAME.put("Opaque", SYNTAX_OPAQUE); SYNTAX_OF_TYPE_NAME.put("OPAQUE", SYNTAX_OPAQUE); TYPE_NAME_OF_SYNTAX.put(SYNTAX_INTEGER, "INTEGER"); TYPE_NAME_OF_SYNTAX.put((int)BER.BITSTRING, "BITS"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_OCTET_STRING, "STRING"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_OBJECT_IDENTIFIER, "OID"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_TIMETICKS, "Timeticks"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_COUNTER32, "Counter32"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_COUNTER64, "Counter64"); TYPE_NAME_OF_SYNTAX.put(EXCEPTION_END_OF_MIB_VIEW, "No more variables left in this MIB View (It is past the end of the MIB tree)"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_GAUGE32, "Gauge32"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_IPADDRESS, "IpAddress"); TYPE_NAME_OF_SYNTAX.put(EXCEPTION_NO_SUCH_INSTANCE, "No Such Instance currently exists at this OID"); TYPE_NAME_OF_SYNTAX.put(EXCEPTION_NO_SUCH_OBJECT, "No Such Object available on this agent at this OID"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_NULL, "NULL"); TYPE_NAME_OF_SYNTAX.put(SYNTAX_OPAQUE, "OPAQUE"); StringBuilder sb = new StringBuilder(); sb.append("("); for (String name: SYNTAX_OF_TYPE_NAME.keySet()) { sb.append(Pattern.quote(name)); sb.append("|"); } sb.deleteCharAt(sb.lastIndexOf("|")); sb.append(")"); REGEX_REGULAR_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + sb.toString() + Pattern.quote(TYPE_VALUE_SEPARATOR) + ".*"; REGEX_EMPTY_STRING_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + "\"\""; REGEX_STRING_HEX_CONTINUATION_PATTERN = "([\\dA-F]{2} )+"; REGEX_BITS_CONTINUATION_PATTERN = "([\\dA-F]{2} )+(\\d+ )*"; REGEX_END_OF_MIB_VIEW_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + Pattern.quote(TYPE_NAME_OF_SYNTAX.get(EXCEPTION_END_OF_MIB_VIEW)); REGEX_NO_SUCH_INSTANCE_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + Pattern.quote(TYPE_NAME_OF_SYNTAX.get(EXCEPTION_NO_SUCH_INSTANCE)); REGEX_NO_SUCH_OBJECT_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + Pattern.quote(TYPE_NAME_OF_SYNTAX.get(EXCEPTION_NO_SUCH_OBJECT)); REGEX_NULL_PATTERN = "([.]\\d+)+" + Pattern.quote(OID_TYPE_SEPARATOR) + Pattern.quote(TYPE_NAME_OF_SYNTAX.get(SYNTAX_NULL)); } private VariableBinding prevStringHexVarBind = null; private VariableBinding prevStringVarBind = null; private VariableBinding prevBitsVarBind = null; /** * Creates a simple variable text format. */ private NetSnmpVariableTextFormat() {} public static NetSnmpVariableTextFormat getInstance() { return new NetSnmpVariableTextFormat(); } public void init() { prevStringHexVarBind = null; prevStringVarBind = null; prevBitsVarBind = null; } /** * Returns a textual representation of the supplied variable against the * optionally supplied instance OID. * * @param instanceOID the instance OID <code>variable</code> is associated * with. If <code>null</code> the formatting cannot take any MIB * specification of the variable into account and has to format it based * on its type only. * @param variable * the variable to format. * @param withOID * if <code>true</code> the <code>instanceOID</code> should be included * in the textual representation to form a {@link VariableBinding} * representation. * @return the textual representation. */ public String format(OID instanceOID, Variable variable, boolean withOID) { Integer syntax = variable.getSyntax(); if (syntax == null) { throw new AssertionError("Never get here."); } String valueString; String typeName = TYPE_NAME_OF_SYNTAX.get(syntax); if (syntax.intValue() == SYNTAX_OCTET_STRING) { if (!is_Printable(((OctetString)variable))) { typeName = "Hex-STRING"; valueString = formatHexStream((OctetString)variable); } else { valueString = "\"" + variable.toString().replaceAll("(\"|\\\\)", "\\\\$1") + "\""; } } else if (syntax.intValue() == SYNTAX_OPAQUE) { valueString = formatHexStream((Opaque)variable); } else if (syntax.byteValue() == BER.BITSTRING) { StringBuilder sb = new StringBuilder(); sb.append(formatHexStream(((BitString)variable))); byte[] octets = ((BitString)variable).getValue(); for (int octetIdx = 0; octetIdx < octets.length; octetIdx++) { int octet = 0xFF & octets[octetIdx]; int mask = 128; for (int bitIdx = 0; bitIdx < 8; bitIdx++) { if ((octet & mask) != 0) { sb.append(octetIdx * 8 + bitIdx) .append(HEX_SEPARATOR); } mask >>= 1; } } valueString = sb.toString(); } else if (syntax.intValue() == SYNTAX_TIMETICKS) { long LongValue = ((TimeTicks)variable).toLong(); long workLongValue = LongValue; long hours = workLongValue / 360000L; workLongValue %= 360000; long minutes = workLongValue / 6000L; workLongValue %= 6000; long seconds = workLongValue / 100L; workLongValue %= 100; long hseconds = workLongValue; valueString = new StringBuilder() .append("(").append(LongValue).append(") ") .append(hours).append(":") .append(String.format("%02d", minutes)).append(":") .append(String.format("%02d", seconds)).append(".") .append(String.format("%02d", hseconds)) .toString(); } else if (syntax.intValue() == SYNTAX_OBJECT_IDENTIFIER) { valueString = DotLedSimpleOIDTextFormat.getInstance() .format(((OID)variable).getValue()); } else if (syntax.intValue() == EXCEPTION_END_OF_MIB_VIEW) { valueString = null; } else if (syntax.intValue() == EXCEPTION_NO_SUCH_INSTANCE) { valueString = null; } else if (syntax.intValue() == EXCEPTION_NO_SUCH_OBJECT) { valueString = null; } else if (syntax.intValue() == SYNTAX_NULL) { valueString = null; } else if (syntax.intValue() == SYNTAX_IPADDRESS) { InetAddress addr = ((IpAddress)variable).getInetAddress(); valueString = addr.toString().replaceAll(".*/", ""); } else if (variable instanceof AssignableFromLong) { valueString = Long.toString(((AssignableFromLong)variable).toLong()); } else if (variable instanceof AssignableFromInteger) { valueString = Integer.toString(((AssignableFromInteger)variable).toInt()); } else { valueString = variable.toString(); } if (withOID) { StringBuilder sb = new StringBuilder(); if (typeName.equals("STRING") && valueString.equals("\"\"")) { sb.append(DotLedSimpleOIDTextFormat.getInstance().format(instanceOID.getValue())) .append(OID_TYPE_SEPARATOR).append(valueString); } else { sb.append(DotLedSimpleOIDTextFormat.getInstance().format(instanceOID.getValue())) .append(OID_TYPE_SEPARATOR).append(typeName); if (valueString != null) { sb.append(TYPE_VALUE_SEPARATOR ).append(valueString); } } return sb.toString(); } else { return valueString; } } private String formatHexStream(OctetString o) { String hexString = o.toHexString(HEX_SEPARATOR).toUpperCase() + HEX_SEPARATOR; if (hexString.length() > STRING_HEX_LENGTH_PER_LINE) { StringBuilder sb = new StringBuilder(); int i; for ( i = 0; i + STRING_HEX_LENGTH_PER_LINE < hexString.length(); i += STRING_HEX_LENGTH_PER_LINE ) { sb.append(hexString.substring(i, i + STRING_HEX_LENGTH_PER_LINE)) .append(STRING_HEX_LINE_SEPARATOR); } sb.append(hexString.substring(i)); return sb.toString(); } else { return hexString; } } /** * This operation is not supported by {@link NetSnmpVariableTextFormat}. * * @param smiSyntax the SMI syntax identifier identifying the target * <code>Variable</code>. * @param text a textual representation of the variable. * @return the new <code>Variable</code> instance. * @throws ParseException if the variable cannot be parsed successfully. */ public Variable parse(int syntax, String value) { Variable v = AbstractVariable.createFromSyntax(syntax); if (v instanceof AssignableFromString) { ((AssignableFromString)v).setValue(value); } else if (syntax == SMIConstants.SYNTAX_NULL) { // Do nothing } else { throw new AssertionError("Never get here."); } return v; } /** * This operation is not supported by {@link NetSnmpVariableTextFormat}. * * @param classOrInstanceOID * the instance OID <code>variable</code> is associated with. Must not * be <code>null</code>. * @param text * a textual representation of the variable. * @return * the new <code>Variable</code> instance. * @throws ParseException * if the variable cannot be parsed successfully. */ public Variable parse(OID classOrInstanceOID, String text) throws ParseException { throw new UnsupportedOperationException(); } public VariableBinding parseVariableBinding(String text) throws ParseException { OID oid; String typeName; String valueString; if (prevStringVarBind != null) { Pattern p = Pattern.compile("([^\\\\]|\\A)((\\\\){2})*\"\\Z"); Matcher m = p.matcher(text); if (m.find()) { OctetString os = (OctetString)prevStringVarBind.getVariable(); os.append( text.substring(0, text.length() - 1).replaceAll("\\\\(.)", "$1") ); VariableBinding vb = prevStringVarBind; prevStringVarBind = null; return vb; } else { OctetString os = (OctetString)prevStringVarBind.getVariable(); os.append(text.replaceAll("\\\\(.)", "$1")); os.append("\n"); // TODO もとの改行コードを復元する return prevStringVarBind; } } else if (prevStringHexVarBind != null) { if (text.matches(REGEX_STRING_HEX_CONTINUATION_PATTERN)) { try { OctetString os = (OctetString)prevStringHexVarBind.getVariable(); os.append(OctetString.fromHexString(text, HEX_SEPARATOR).getValue()); return prevStringHexVarBind; } catch (NumberFormatException e) { throw new ParseException("Invalid format: "+ text, 0); } } } else if (prevBitsVarBind != null) { if (text.matches(REGEX_BITS_CONTINUATION_PATTERN)) { try { prevBitsVarBind .setVariable( parseBits( ((BitString)prevBitsVarBind.getVariable()) .toHexString(HEX_SEPARATOR) + HEX_SEPARATOR + text ) ); return prevBitsVarBind; } catch (ParseException e) { throw new ParseException("Invalid format: "+ text, 0); } } } if (text.matches(REGEX_REGULAR_PATTERN)) { String[] tokens = text.split(OID_TYPE_SEPARATOR, 2); if (tokens.length != 2) { throw new AssertionError("Never get here"); } oid = new OID( DotLedSimpleOIDTextFormat.getInstance().parse(tokens[0]) ); tokens = tokens[1].split(TYPE_VALUE_SEPARATOR, 2); if (tokens.length != 2) { throw new AssertionError("Never get here"); } typeName = tokens[0]; valueString = tokens[1]; } else if (text.matches(REGEX_EMPTY_STRING_PATTERN)) { String[] tokens = text.split(OID_TYPE_SEPARATOR, 2); if (tokens.length != 2) { throw new AssertionError("Never get here"); } oid = new OID( DotLedSimpleOIDTextFormat.getInstance().parse(tokens[0]) ); typeName = "STRING"; valueString = "\"\""; } else if (text.matches(REGEX_END_OF_MIB_VIEW_PATTERN)) { return null; } else if (text.matches(REGEX_NO_SUCH_INSTANCE_PATTERN)) { return null; } else if (text.matches(REGEX_NO_SUCH_OBJECT_PATTERN)) { return null; } else if (text.matches(REGEX_NULL_PATTERN)) { String[] tokens = text.split(OID_TYPE_SEPARATOR, 2); if (tokens.length != 2) { throw new AssertionError("Never get here"); } oid = new OID( DotLedSimpleOIDTextFormat.getInstance().parse(tokens[0]) ); typeName = tokens[1]; valueString = null; } else { throw new ParseException("Invalid format: "+ text, 0); } VariableBinding vb; if (typeName.equals("Hex-STRING")) { OctetString os = (OctetString)AbstractVariable.createFromSyntax(SYNTAX_OCTET_STRING); os.setValue( OctetString.fromHexString(valueString, HEX_SEPARATOR).getValue() ); vb = new VariableBinding(oid, os); } else if (typeName.equals("STRING")) { if (!valueString.startsWith("\"")) { throw new ParseException("Invalid format: "+ text, 0); } OctetString os = (OctetString)AbstractVariable.createFromSyntax(SYNTAX_OCTET_STRING); Pattern p = Pattern.compile("[^\\\\]((\\\\){2})*\"\\Z"); Matcher m = p.matcher(valueString); if (valueString.length() > 1 && m.find()) { os.setValue( valueString .substring(1, valueString.length() - 1) .replaceAll("\\\\(.)", "$1") ); vb = new VariableBinding(oid, os); } else { os.setValue(valueString.substring(1).replaceAll("\\\\(.)", "$1")); os.append("\n"); // TODO もとの改行コードを復元する vb = new VariableBinding(oid, os); prevStringVarBind = vb; } } else if (typeName.equals("BITS")) { try { vb = new VariableBinding(oid, parseBits(valueString)); } catch (ParseException e) { throw new ParseException("Invalid format: "+ text, 0); } } else if (typeName.equals("Timeticks")) { vb = new VariableBinding( oid, parse( SYNTAX_OF_TYPE_NAME.get(typeName), valueString.substring( valueString.indexOf('(') + 1, valueString.indexOf(')') ) ) ); } else if (typeName.equals("Network Address")) { IpAddress ipAddr = (IpAddress)AbstractVariable.createFromSyntax(SYNTAX_IPADDRESS); ipAddr.setValue( OctetString.fromHexString( valueString, NETWORK_ADDRESS_SEPARATOR ).getValue() ); vb = new VariableBinding(oid, ipAddr); } else if (typeName.equals("Opaque")) { logger.warn("The result of parsing Opaque may be incorrect: " + text); vb = new VariableBinding( oid, parse(SYNTAX_OF_TYPE_NAME.get(typeName), valueString) ); } else { vb = new VariableBinding( oid, parse(SYNTAX_OF_TYPE_NAME.get(typeName), valueString) ); } prevStringHexVarBind = typeName.equals("Hex-STRING") ? vb : null; prevBitsVarBind = typeName.equals("BITS") ? vb : null; return vb; } private boolean is_Printable(OctetString o) { for (byte b: o.getValue()) { switch(b) { case 0x09: // HT case 0x0a: // LF case 0x0b: // VT case 0x0c: // NP(New Page) case 0x0d: // CR continue; default: if (b < 0x20 || b > 0x7e) { return false; } } } return true; } private BitString parseBits(String valueString) throws ParseException { String[] strOctets = valueString.split(String.valueOf(HEX_SEPARATOR)); StringBuilder valuePattern = new StringBuilder(); StringBuilder parsedOctets = new StringBuilder(); boolean match = false; for (int octIdx = 0; octIdx < strOctets.length; octIdx++) { int octet; try { octet = Integer.parseInt(strOctets[octIdx], 16); parsedOctets.append(strOctets[octIdx]).append(HEX_SEPARATOR); } catch (NumberFormatException e) { throw new ParseException("Invalid value for BITS: "+ valueString, octIdx); } int mask = 128; for (int bitIdx = 0; bitIdx < 8; bitIdx++) { if ((octet & mask) != 0) { valuePattern.append(octIdx * 8 + bitIdx).append(HEX_SEPARATOR); } mask >>= 1; } if ( valueString.matches(parsedOctets + "(00 )*" + valuePattern) ) { match = true; break; } } BitString bs = (BitString)AbstractVariable.createFromSyntax(BER.ASN_BIT_STR); if (match) { bs.setValue( OctetString.fromHexString( valueString.substring( 0, valueString.lastIndexOf(valuePattern.toString()) ), HEX_SEPARATOR ).getValue() ); } else { bs.setValue( OctetString.fromHexString( valueString, HEX_SEPARATOR ).getValue() ); } return bs; } }