<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/VariableModifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.oneandone.snmpman.configuration.type.ModifierProperties;
import org.snmp4j.smi.Variable;

/** Abstract definition of a variable modifier. 
 * @param <T> the variable type that gets modified by this instance.
 */
public interface VariableModifier<T extends Variable> {

    /**
     * Modifies the specified {@code variable} and returns a copy of it.
     * 
     * @param variable the variable to modify
     * @return the modified variable
     */
    T modify(final T variable);

    /**
     * Initialize {@code this} variable modifier by the specified {@code properties}.
     *
     * @param properties the initialization properties
     */
    default void init(final ModifierProperties properties) { }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/type/ModifierProperties.java<|end_filename|>
package com.oneandone.snmpman.configuration.type;

import com.google.common.primitives.UnsignedLong;

import java.util.Optional;
import java.util.Properties;

/** The modifier properties as used in {@link com.oneandone.snmpman.configuration.Device} for initialization. */
public class ModifierProperties extends Properties {

    /**
     * Returns the integer value for the specified property {@code key}.
     *  
     * @param key the property key
     * @return the integer value for the specified key or {@code null} if not existing.
     * @throws java.lang.ClassCastException thrown if the value for the specified key could not be casted
     */
    public Integer getInteger(final String key) {
        final Optional<Number> number = getNumber(key);
        return number.isPresent() ? number.get().intValue() : null;
    }

    /**
     * Returns the long value for the specified property {@code key}.
     *
     * @param key the property key
     * @return the long value for the specified key or {@code null} if not existing.
     * @throws java.lang.ClassCastException thrown if the value for the specified key could not be casted
     */
    public Long getLong(final String key) {
        final Optional<Number> number = getNumber(key);
        return number.isPresent() ? number.get().longValue() : null;
    }

    /**
     * Returns the unsigned long value for the specified property {@code key}.
     *
     * @param key the property key
     * @return the unsigned long value for the specified key or {@code null} if not existing.
     * @throws java.lang.ClassCastException thrown if the value for the specified key could not be casted
     */
    public UnsignedLong getUnsignedLong(final String key) {
        final Optional<Number> number = getNumber(key);
        return number.map(value -> UnsignedLong.valueOf(value.longValue())).orElse(null);
    }

    /**
     * Returns the number for the specified property {@code key}.
     *  
     * @param key the property key
     * @return the optional number for the specified key.
     * @throws java.lang.ClassCastException thrown if the value for the specified key could not be casted
     */
    private Optional<Number> getNumber(final String key) {
        if (this.containsKey(key)) {
            final Object value = this.get(key);
            if (value instanceof Number) {
                return Optional.of((Number) value);
            } else if (value instanceof String) {
                return Optional.of(Long.valueOf((String) value));
            } else {
                throw new ClassCastException("property \"" + key + "\" is not a number");
            }
        }
        return Optional.empty();
    }
}


<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/type/WildcardOIDTest.java<|end_filename|>
package com.oneandone.snmpman.configuration.type;

import org.snmp4j.smi.OID;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

public class WildcardOIDTest {

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testIllegalWildcard() {
        new WildcardOID(".1.2.3.*.*.4");
    }
    
    @Test
    public void testStartingMatcher() {
        final WildcardOID wildcard = new WildcardOID(".1.2.3.*");
        
        assertTrue(wildcard.matches(new OID("1.2.3.4")));
        assertTrue(wildcard.matches(new OID("1.2.3.4.5")));
        assertFalse(wildcard.matches(new OID("1.2.4")));
        
        assertEquals(wildcard.toString(), "1.2.3.*");
    }
    
    @Test
    public void testGeneralMatcher() {
        final WildcardOID wildcard = new WildcardOID(".1.2.3.*.5");

        assertTrue(wildcard.matches(new OID("1.2.3.4.5")));
        assertTrue(wildcard.matches(new OID("1.2.3.1337.5")));
        assertFalse(wildcard.matches(new OID("1.2.3.4")));
        assertFalse(wildcard.matches(new OID("1.2.3.4.4")));
        
        assertEquals(wildcard.toString(), "1.2.3.*.5");
    }
}

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/Snmpman.java<|end_filename|>
package com.oneandone.snmpman;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.google.common.base.Preconditions;
import com.oneandone.snmpman.configuration.AgentConfiguration;
import com.oneandone.snmpman.exception.InitializationException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.agent.BaseAgent;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This is the library interface for Snmpman.
 * <br>
 * Each configuration list item represents an instance of the {@link com.oneandone.snmpman.configuration.AgentConfiguration}.
 * The constructor {@link com.oneandone.snmpman.configuration.AgentConfiguration#AgentConfiguration(String, java.io.File, java.io.File, String, int, String)}
 * lists all available properties, which may or may not be required.
 * <br>
 * An entry may look like the following:
 * <pre>
 * {@code
 *     - name: "example1"
 *       device: "src/test/resources/configuration/cisco.yaml"
 *       walk: "src/test/resources/configuration/example.txt"
 *       ip: "127.0.0.1"
 *       port: 10000
 * }
 * </pre>
 * You can find more example within the test resources of this project.
 * <br>
 * The configuration {@code YAML} file defines a list of all agents that should be simulated by the {@code Snmpman}.
 */
@Slf4j
public final class Snmpman {

    /**
     * Returns the list of SNMP agents for {@code this} instance.
     *
     * @return the list of SNMP agents
     */
    @Getter private final List<SnmpmanAgent> agents;

    /**
     * Constructs an instance by the specified list of agents.
     *
     * @param agents the agents for {@code this} instance
     */
    private Snmpman(final List<SnmpmanAgent> agents) {
        this.agents = agents;
    }

    /**
     * Creates an {@code Snmpman} instance by the specified configuration in the {@code configurationFile} and starts all agents.
     *
     * @param configurationFile the configuration
     * @return the {@code Snmpman} instance
     * @throws com.oneandone.snmpman.exception.InitializationException thrown if any agent, as specified in the configuration, could not be started
     */
    public static Snmpman start(final File configurationFile) {
        Preconditions.checkNotNull(configurationFile, "the configuration file may not be null");
        Preconditions.checkArgument(configurationFile.exists() && configurationFile.isFile(), "configuration does not exist or is not a file");

        log.debug("started with configuration in path {}", configurationFile.getAbsolutePath());
        try {
            final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            final AgentConfiguration[] configurations = mapper.readValue(configurationFile, AgentConfiguration[].class);

            return Snmpman.start(Arrays.stream(configurations).map(SnmpmanAgent::new).collect(Collectors.toList()));
        } catch (final IOException e) {
            throw new InitializationException("could not parse configuration at path: " + configurationFile.getAbsolutePath(), e);
        }
    }

    /**
     * Creates a {@code Snmpman} instance with the specified list of agents and starts all agents.
     *
     * @param agents the list of agents
     * @return the {@code Snmpman} instance
     * @throws com.oneandone.snmpman.exception.InitializationException thrown if any agent, as specified in the configuration, could not be started
     */
    public static Snmpman start(final List<SnmpmanAgent> agents) {
        final Snmpman snmpman = new Snmpman(Collections.unmodifiableList(agents));
        snmpman.start();
        return snmpman;
    }

    /**
     * Starts all agents as defined in {@link #agents}.
     *
     * @throws com.oneandone.snmpman.exception.InitializationException thrown if any agent could not be started
     */
    private void start() {
        log.debug("starting to load agents");
        for (final SnmpmanAgent agent : agents) {
            try {
                agent.execute();
            } catch (final IOException e) {
                throw new InitializationException("failed to start agent \"" + agent.getName() + "\"", e);
            }
        }
        log.debug("all agents initialized");
        agents.forEach(this::checkStatus);
        log.debug("all agents are running");
    }

    /**
     * Wait until specified agent is started.
     * <br>
     * A call of this method is blocking.
     *
     * @param agent the agent to wait for
     * @throws InitializationException if the specified agent is already stopped
     */
    private void checkStatus(final SnmpmanAgent agent) {
        if (agent.getAgentState() == BaseAgent.STATE_STOPPED) {
            throw new InitializationException("agent " + agent.getName() + " already stopped while initialization was running");
        } else if (agent.getAgentState() != BaseAgent.STATE_RUNNING) {
            try {
                Thread.sleep(100L);
                checkStatus(agent);
            } catch (final InterruptedException e) {
                log.warn("wait was interrupted", e);
            }
        }
    }

    /** Stops all agents as defined in {@link #agents}. */
    public void stop() {
        agents.forEach(SnmpmanAgent::stop);
    }
}


<|start_filename|>snmpman-cli/src/main/java/com/oneandone/snmpman/Main.java<|end_filename|>
package com.oneandone.snmpman;

import com.oneandone.snmpman.exception.InitializationException;
import lombok.extern.slf4j.Slf4j;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

/**
 * This is the main-class for this application.
 * <br>
 * A mandatory argument for execution of the {@link #main(String...)} method is the path to the configuration file.
 * See {@link com.oneandone.snmpman.CommandLineOptions} for information on available command line options.
 * <br>
 * Each configuration list item represents an instance of the {@link com.oneandone.snmpman.configuration.AgentConfiguration}.
 * The constructor {@link com.oneandone.snmpman.configuration.AgentConfiguration#AgentConfiguration(String, java.io.File, java.io.File, String, int, String)}
 * lists all available properties, which may or may not be required.
 * <br>
 * An entry may look like the following:
 * <pre>
 * {@code
 *     - name: "example1"
 *       device: "src/test/resources/configuration/cisco.yaml"
 *       walk: "src/test/resources/configuration/example.txt"
 *       ip: "127.0.0.1"
 *       port: 10000
 * }
 * </pre>
 * You can find more example within the test resources of this project.
 * <br>
 * The configuration {@code YAML} file defines a list of all agents that should be simulated by the {@code Main}.
 */
@Slf4j
public final class Main {

    /**
     * The application entry-point.
     * <br>
     * All available command-line arguments are documented in the {@link com.oneandone.snmpman.CommandLineOptions} class.
     * <br>
     * If illegal command-line options were specified for execution, a usage help message will be printed out
     * on the {@link System#err} stream and the application will terminate. Otherwise the configuration will
     * be read and used for execution.
     *
     * @param args the command-line arguments
     */
    public static void main(final String... args) {
        final CommandLineOptions commandLineOptions = new CommandLineOptions();
        final CmdLineParser cmdLineParser = new CmdLineParser(commandLineOptions);
        try {
            cmdLineParser.parseArgument(args);

            if (commandLineOptions.isShowHelp()) {
                cmdLineParser.printUsage(System.out);
            } else {
                Snmpman.start(commandLineOptions.getConfigurationFile());
            }
        } catch (final InitializationException | CmdLineException e) {
            log.error("could not parse or process command-line arguments", e);
            if (e.getMessage() != null && !e.getMessage().isEmpty()) {
                System.err.println("could not start application because of following error: ");
                System.err.println(e.getMessage());
            } else {
                System.err.println("failed to start application, check the logs for more information");
            }
            cmdLineParser.printUsage(System.err);
        }
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/type/package-info.java<|end_filename|>
/** Types as used by other configuration representations. */
package com.oneandone.snmpman.configuration.type;

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/TimeTicksModifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import org.snmp4j.smi.TimeTicks;

/**
 * This modifier instance modifies {@link TimeTicks} variables by the {@link #modify(TimeTicks)} method.
 * <br>
 * This method will return a new {@link TimeTicks} instance with the current timestamp.
 */
public class TimeTicksModifier implements VariableModifier<TimeTicks> {

    /** The initialization time of {@code this} modifier. */
    private final long initTime = System.currentTimeMillis();

    @Override
    public TimeTicks modify(final TimeTicks variable) {
        TimeTicks timeTicks = new TimeTicks();
        final long timeTicksInMilliseconds = variable.toMilliseconds();
        final long upTime = (System.currentTimeMillis() - initTime) + timeTicksInMilliseconds;
        timeTicks.fromMilliseconds(timeTicksInMilliseconds + upTime);
        return timeTicks;
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/AbstractIntegerModifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.oneandone.snmpman.configuration.type.ModifierProperties;
import lombok.Getter;
import org.snmp4j.smi.UnsignedInteger32;

import java.util.Optional;

/**
 * This modifier has all utility methods to construct for unsigned integer variable modifiers.
 */
abstract class AbstractIntegerModifier<T extends UnsignedInteger32> implements VariableModifier<T> {

    /** The minimum allowed number for the resulting modified variable. */
    @Getter private long minimum;

    /** The maximum allowed number for the resulting modified variable. */
    @Getter private long maximum;

    /** The minimal step by which a variable will be incremented. */
    @Getter private long minimumStep;

    /** The maximal step by which a variable will be incremented. */
    @Getter private long maximumStep;

    @Override
    public void init(final ModifierProperties properties) {
        this.minimum = Optional.ofNullable(properties.getLong("minimum")).orElse(0L);
        this.maximum = Optional.ofNullable(properties.getLong("maximum")).orElse(UnsignedInteger.MAX_VALUE.longValue());

        this.minimumStep = Optional.ofNullable(properties.getLong("minimumStep")).orElse(0L);
        this.maximumStep = Optional.ofNullable(properties.getLong("maximumStep")).orElse(1L);

        Preconditions.checkArgument(minimum >= 0, "minimum should not be negative");
        Preconditions.checkArgument(maximum >= 0, "maximum should not be negative");

        Preconditions.checkArgument(minimum <= UnsignedInteger.MAX_VALUE.longValue(), "minimum should not exceed 2^32-1 (4294967295 decimal)");
        Preconditions.checkArgument(maximum <= UnsignedInteger.MAX_VALUE.longValue(), "maximum should not exceed 2^32-1 (4294967295 decimal)");
    }

    /**
     * Casts the long value to the specified output type of the implementing modifier.
     *
     * @param value the value to cast
     * @return the casted value
     */
    protected abstract T cast(long value);

    @Override
    public final T modify(final T variable) {
        long currentValue = variable.getValue();
        if (currentValue < minimum || currentValue > maximum) {
            currentValue = minimum;
        }
        long step = (Math.round(Math.random() * (maximumStep - minimumStep)) + minimumStep);

        long stepUntilMaximum = maximum - currentValue;
        long newValue;
        if (Math.abs(step) > Math.abs(stepUntilMaximum)) {
            newValue = minimum + (step - stepUntilMaximum - 1);
        } else {
            newValue = currentValue + step;
        }

        if (newValue < minimum) {
            newValue = minimum;
        } else if (newValue > maximum) {
            newValue = maximum;
        }

        return cast(newValue);
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/UnsignedInteger32Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import org.snmp4j.smi.UnsignedInteger32;

/** This modifier instance modifies {@link org.snmp4j.smi.UnsignedInteger32} variables. */
public class UnsignedInteger32Modifier extends AbstractIntegerModifier<UnsignedInteger32> {

    @Override
    protected UnsignedInteger32 cast(long value) {
        return new UnsignedInteger32(value);
    }
}


<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/modifier/ModifiedVariableTest.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.oneandone.snmpman.configuration.type.ModifierProperties;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.Variable;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;

import static org.testng.Assert.*;

public class ModifiedVariableTest {

    private Variable variable;
    private Variable otherVariable;
    private ModifiedVariable modifiedVariable;
    
    @BeforeMethod
    public void setUp() {
        variable = new Integer32(0);
        otherVariable = new Integer32(1);
        
        List<VariableModifier> modifiers = new ArrayList<>(1);

        final ModifierProperties modifierProperties = new ModifierProperties();
        modifierProperties.put("minimum", Integer.MIN_VALUE);
        modifierProperties.put("maximum", Integer.MAX_VALUE);
        modifierProperties.put("minimumStep", 1);
        modifierProperties.put("maximumStep", 10);

        final Integer32Modifier modifier = new Integer32Modifier();
        modifier.init(modifierProperties);
        
        modifiers.add(modifier);
        
        modifiedVariable = new ModifiedVariable(variable, modifiers);
    }
    
    @Test
    public void testCompareTo() {
        assertEquals(variable.compareTo(otherVariable), modifiedVariable.compareTo(otherVariable));
    }

    @Test
    public void testClone() {
        final Object clonedVariable = modifiedVariable.clone();
        assertNotSame(clonedVariable, modifiedVariable);
        assertTrue(clonedVariable instanceof Integer32);
        
        assertNotEquals(((Integer32) clonedVariable).getValue(), ((Integer32) variable).getValue());
    }

    @Test
    public void testGetSyntax() {
        assertEquals(modifiedVariable.getSyntax(), variable.getSyntax());
    }

    @Test
    public void testIsException() {
        assertEquals(modifiedVariable.isException(), variable.isException());
    }

    @Test
    public void testToString() {
        assertEquals(modifiedVariable.toString(), variable.toString());
    }

    @Test
    public void testToInt() {
        assertEquals(modifiedVariable.toInt(), variable.toInt());
    }

    @Test
    public void testToLong() {
        assertEquals(modifiedVariable.toLong(), variable.toLong());
    }

    @Test
    public void testGetSyntaxString() {
        assertEquals(modifiedVariable.getSyntaxString(), variable.getSyntaxString());
    }

    @Test
    public void testToSubIndex() {
        assertEquals(modifiedVariable.toSubIndex(true), variable.toSubIndex(true));
        assertEquals(modifiedVariable.toSubIndex(false), variable.toSubIndex(false));
    }

    @Test
    public void testIsDynamic() {
        assertEquals(modifiedVariable.isDynamic(), variable.isDynamic());
    }

    @Test
    public void testGetBERLength() {
        assertEquals(modifiedVariable.getBERLength(), variable.getBERLength());
    }

    @Test
    public void testGetBERPayloadLength() {
        assertEquals(modifiedVariable.getBERPayloadLength(), variable.getBERPayloadLength());
    }

    @Test
    public void testEquals() {
        assertTrue(modifiedVariable.equals(modifiedVariable));
        assertTrue(modifiedVariable.equals(variable));
    }

    @Test
    public void testHashCode() {
        assertEquals(modifiedVariable.hashCode(), variable.hashCode());
    }
}

<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/modifier/AbstractIntegerModifierTest.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.oneandone.snmpman.configuration.type.ModifierProperties;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

public class AbstractIntegerModifierTest {

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testIllegalInitParameterMinimum() {
        final ModifierProperties modifierProperties = new ModifierProperties();
        modifierProperties.put("minimum", -1);
        modifierProperties.put("maximum", 3000);
        modifierProperties.put("minimumStep", 1);
        modifierProperties.put("maximumStep", 10);

        final Gauge32Modifier modifier = new Gauge32Modifier();
        modifier.init(modifierProperties);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testIllegalInitParameterMaximum() {
        final ModifierProperties modifierProperties = new ModifierProperties();
        modifierProperties.put("minimum", 0);
        modifierProperties.put("maximum", -1);
        modifierProperties.put("minimumStep", 1);
        modifierProperties.put("maximumStep", 10);

        final Gauge32Modifier modifier = new Gauge32Modifier();
        modifier.init(modifierProperties);
    }

    @Test
    public void testProperties() {
        final ModifierProperties modifierProperties = new ModifierProperties();
        modifierProperties.put("minimum", 0);
        modifierProperties.put("maximum", 10);
        modifierProperties.put("minimumStep", 1);
        modifierProperties.put("maximumStep", 10);

        final Gauge32Modifier modifier = new Gauge32Modifier();
        modifier.init(modifierProperties);

        assertEquals(modifier.getMinimum(), 0);
        assertEquals(modifier.getMaximum(), 10);
        assertEquals(modifier.getMinimumStep(), 1);
        assertEquals(modifier.getMaximumStep(), 10);
    }
    
}

<|start_filename|>snmpman-cli/src/main/java/com/oneandone/snmpman/CommandLineOptions.java<|end_filename|>
package com.oneandone.snmpman;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.kohsuke.args4j.Option;

import java.io.File;

/** The command-line options for the {@link Main} application. */
@Slf4j
public final class CommandLineOptions {

    /** The {@code Main} configuration. */
    @Option(name = "-c", aliases = "--configuration", usage = "the path to the configuration YAML", required = true)
    @Getter private File configurationFile;

    /** Flag that defines that a help message should be displayed when {@code true}. */
    @Option(name = "-h", aliases = "--help", usage = "print the help message", help = true)
    @Getter private boolean showHelp = false;
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/package-info.java<|end_filename|>
/**
 * This package contains all components to represent modified {@link org.snmp4j.smi.Variable}.
 * <br>
 * In this context the so called modified variables are those which value change with every prompt.
 * <br>
 * Every implementation of the {@link com.oneandone.snmpman.configuration.modifier.VariableModifier} can be used as a binding
 * modifier in a device configuration.
 *
 * @see com.oneandone.snmpman.configuration.Device
 */
package com.oneandone.snmpman.configuration.modifier;

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/Integer32Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.oneandone.snmpman.configuration.type.ModifierProperties;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.smi.Integer32;

import java.util.Optional;

/** This modifier instance modifies {@link Integer32} variables. */
@Slf4j
public class Integer32Modifier implements VariableModifier<Integer32> {

    /** The minimum allowed number for the resulting modified variable. */
    @Getter private Integer minimum;

    /** The maximum allowed number for the resulting modified variable. */
    @Getter private Integer maximum;

    /** The minimal step by which a variable will be incremented. */
    @Getter private Integer minimumStep;

    /** The maximal step by which a variable will be incremented. */
    @Getter private Integer maximumStep;

    @Override
    public void init(final ModifierProperties properties) {
        this.minimum = Optional.ofNullable(properties.getInteger("minimum")).orElse(Integer.MIN_VALUE);
        this.maximum = Optional.ofNullable(properties.getInteger("maximum")).orElse(Integer.MAX_VALUE);

        this.minimumStep = Optional.ofNullable(properties.getInteger("minimumStep")).orElse(-1);
        this.maximumStep = Optional.ofNullable(properties.getInteger("maximumStep")).orElse(1);
    }

    /**
     * Increments the current value by a random number between the minimum and maximum step.
     * <p>
     * An overflow can occur and will be considered in the minimum and maximum interval.
     *
     * @param currentValue the current value to modify
     * @param minimum      {@link #minimum}
     * @param maximum      {@link #maximum}
     * @param minimumStep  {@link #minimumStep}
     * @param maximumStep  {@link #maximumStep}
     * @return the modified variable value
     */
    protected int modify(final int currentValue, final int minimum, final int maximum, final int minimumStep, final int maximumStep) {
        int currentValidValue = currentValue;
        if (currentValue < minimum || currentValue > maximum) {
            currentValidValue = minimum;
        }
        int step = (int) (Math.round(Math.random() * (maximumStep - minimumStep)) + minimumStep);

        int stepUntilMaximum = maximum - currentValidValue;
        int newValue;
        if (Math.abs(step) > Math.abs(stepUntilMaximum)) {
            newValue = minimum + (step - stepUntilMaximum - 1);
        } else {
            newValue = currentValidValue + step;
        }

        if (newValue < minimum) {
            newValue = minimum;
        } else if (newValue > maximum) {
            newValue = maximum;
        }

        return newValue;
    }

    @Override
    public Integer32 modify(final Integer32 variable) {
        final int newValue = this.modify(variable.getValue(), minimum, maximum, minimumStep, maximumStep);
        log.trace("Counter32 variable {} will be tuned to {}", variable.getValue(), newValue);
        return new Integer32(newValue);
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/package-info.java<|end_filename|>
/** This package contains all classes as used for the configuration of the {@code Snmpman} application. */
package com.oneandone.snmpman.configuration;

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/CommunityContextModifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;

import java.util.Map;

/**
 * This is a {@link VariableModifier}, which is able to handle the SNMP community context.
 */
public interface CommunityContextModifier<T extends Variable> extends VariableModifier<T> {
    /**
     * Getter for the mapping of the community context to value mapping.
     *
     * @return Map of context value mapping.
     */
    Map<Long, Long> getCommunityContextMapping();

    /**
     * Creates a mapping of SNMP OID and a value based on the SNMP community context and the SNMP OID.
     *
     * @param context SNMP community context.
     * @param oid     SNMP OID.
     * @return variable binding of OID to Variable.
     */
    Map<OID, Variable> getVariableBindings(final OctetString context, final OID oid);
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/exception/package-info.java<|end_filename|>
/** Application specific exception types. */
package com.oneandone.snmpman.exception;

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/type/WildcardOID.java<|end_filename|>
package com.oneandone.snmpman.configuration.type;

import com.google.common.base.Preconditions;
import lombok.EqualsAndHashCode;
import org.snmp4j.smi.OID;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A wildcard {@code OID} is an {@code OID} that can contain the wildcard character "*".
 * <br>
 * The wildcard will be considered for matching request in the function {@link #matches(org.snmp4j.smi.OID)}.
 * Every sequence of {@code int} array chains can be considered for the wildcard character.
 * <br>
 * Only one wildcard is allowed for the wildcard {@code OID}.
 */
@EqualsAndHashCode
public class WildcardOID {

    /** The wildcard OID pattern. */
    private static final Pattern WILDCARD_OID_PATTERN = Pattern.compile("((\\.)?[0-9]+(\\.[0-9]+)*)(\\.\\*)?((\\.[0-9]+)*)");

    /** The first part of the wildcard {@code OID} (before the "{@code *}" character). */
    private final OID startsWith;

    /** The second part of the wildcard {@code OID} (after the "{@code *}" character). Can be {@code null}. */
    private final OID endsWith;

    /**
     * Constructs a new instance of this class.
     *
     * @param oid the {@code OID} that potentially contains a wildcard
     * @throws NullPointerException     if the specified {@code OID} is null
     * @throws IllegalArgumentException if the specified {@code OID} contains more than one wildcard character
     */
    public WildcardOID(final String oid) {
        Preconditions.checkNotNull(oid, "oid may not be null");

        final Matcher matcher = WILDCARD_OID_PATTERN.matcher(oid);
        if (matcher.matches()) {
            this.startsWith = new OID(matcher.group(1));
            if (matcher.group(5).isEmpty()) {
                this.endsWith = null;
            } else {
                this.endsWith = new OID(matcher.group(5));
            }
        } else {
            throw new IllegalArgumentException("specified oid \"" + oid + "\" is not a valid wildcard");
        }
    }

    /**
     * Returns {@code true} if this wildcard {@code OID} matches with the specified {@code OID}.
     * <br>
     * Here some examples for matching {@code OID}s:
     * <ul>
     * <li>{@code .1.3.3.*.7} and {@code .1.3.3.7}</li>
     * <li>{@code .1.3.3.*.7} and {@code .1.3.3.3.7}</li>
     * <li>{@code .1.3.3.*.7} and {@code .1.3.3.1.2.3.4.5.7}</li>
     * </ul>
     *
     * @param oid the {@code OID} to test
     * @return {@code true} if the {@code OID}s are matching, otherwise {@code false}
     */
    public boolean matches(final OID oid) {
        return oid.startsWith(startsWith) && (endsWith == null || oid.size() >= endsWith.size() && oid.rightMostCompare(endsWith.size(), endsWith) == 0);
    }

    @Override
    public String toString() {
        if (endsWith == null) {
            return startsWith.toDottedString() + ".*";
        } else {
            return startsWith.toDottedString() + ".*." + endsWith.toDottedString();
        }
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.oneandone.snmpman.configuration.type.ModifierProperties;
import com.oneandone.snmpman.configuration.type.WildcardOID;
import com.oneandone.snmpman.exception.InitializationException;
import lombok.Getter;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.Variable;

import java.lang.reflect.InvocationTargetException;

/**
 * Representation of a generic modifier.
 * <br>
 * See also {@link com.oneandone.snmpman.configuration.modifier.VariableModifier}.
 *
 * @param <T> the variable type
 */
public class Modifier<T extends Variable> implements VariableModifier<T> {

    /** The OID range {@code this} modifier should process. */
    private final WildcardOID oid;

    /**
     * The wrapped variable modifier for this generic modifier.
     *
     * @return variable modifier.
     */
    @Getter private final VariableModifier<T> modifier;

    /**
     * Constructs a wrapped modifier.
     *
     * @param oid the wildcard OID to define the OID range {@code this} modifier should process
     * @param modifierClass the class of the modifier
     * @param properties the initialization properties
     */
    @SuppressWarnings("unchecked")
    public Modifier(@JsonProperty("oid") final String oid,
                    @JsonProperty("class") final String modifierClass,
                    @JsonProperty("properties") final ModifierProperties properties) {
        this.oid = new WildcardOID(oid);

        try {
            this.modifier = (VariableModifier) Class.forName(modifierClass).getConstructor().newInstance();
            this.modifier.init(properties);
        } catch (final ClassNotFoundException | NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) {
            throw new InitializationException("could not construct a modifier for the oid " + oid + " and class " + modifierClass, e);
        }
    }

    /**
     * Returns {@code true} if this modifier is applicable for the specified {@code oid}, otherwise {@code false}.
     *
     * @param oid the oid to check
     * @return {@code true} if this modifier is applicable for the specified {@code oid}, otherwise {@code false}
     */
    public boolean isApplicable(final OID oid) {
        return this.oid.matches(oid);
    }

    @Override
    public T modify(T variable) {
        return modifier.modify(variable);
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/Counter32Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import org.snmp4j.smi.Counter32;

/** This modifier instance modifies {@link org.snmp4j.smi.Counter32} variables. */
public class Counter32Modifier extends AbstractIntegerModifier<Counter32> {

    @Override
    protected Counter32 cast(long value) {
        return new Counter32(value);
    }
}


<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/type/ModifierPropertiesTest.java<|end_filename|>
package com.oneandone.snmpman.configuration.type;

import com.google.common.primitives.UnsignedLong;
import com.oneandone.snmpman.configuration.AgentConfiguration;
import com.oneandone.snmpman.configuration.Device;
import com.oneandone.snmpman.configuration.modifier.Counter64Modifier;
import com.oneandone.snmpman.configuration.modifier.Modifier;
import org.snmp4j.smi.OID;
import org.testng.annotations.Test;

import java.io.File;
import java.lang.reflect.Field;
import java.math.BigInteger;

import static org.testng.Assert.*;

public class ModifierPropertiesTest {

    @Test
    public void testParsing() throws Exception {
        final AgentConfiguration.DeviceFactory deviceFactory = new AgentConfiguration.DeviceFactory();
        final Device device = deviceFactory.getDevice(new File("src/test/resources/configuration/cisco.yaml"));

        assertFalse(device.getModifiers().isEmpty());

        boolean counter64found = false;
        for (final Modifier modifier : device.getModifiers()) {
            // counter64
            if (modifier.isApplicable(new OID(".1.3.6.1.2.1.31.1.1.1.11.1"))) {
                final Field variableModifierField = Modifier.class.getDeclaredField("modifier");
                variableModifierField.setAccessible(true);

                final Counter64Modifier counter64Modifier = (Counter64Modifier) variableModifierField.get(modifier);
                assertEquals(counter64Modifier.getMaximum(), UnsignedLong.valueOf(new BigInteger("1844674407370955161")));
                counter64found = true;
            }
        }
        
        if (!counter64found) {
            fail("no modifier for unsigned long found");
        }
    }
    
}

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/Gauge32Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import org.snmp4j.smi.Gauge32;

/** This modifier instance modifies {@link org.snmp4j.smi.Gauge32} variables. */
public class Gauge32Modifier extends AbstractIntegerModifier<Gauge32> {

    @Override
    protected Gauge32 cast(long value) {
        return new Gauge32(value);
    }
}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/configuration/modifier/Counter64Modifier.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.google.common.primitives.UnsignedLong;
import com.oneandone.snmpman.configuration.type.ModifierProperties;
import lombok.Getter;
import org.snmp4j.smi.Counter64;

import java.util.Optional;

/** This modifier instance modifies {@link Counter64} variables. */
public class Counter64Modifier implements VariableModifier<Counter64> {

    /** The minimum allowed number for the resulting modified variable. */
    @Getter private UnsignedLong minimum;

    /** The maximum allowed number for the resulting modified variable. */
    @Getter private UnsignedLong maximum;

    /** The minimal step by which a variable will be incremented. */
    @Getter private UnsignedLong minimumStep;

    /** The maximal step by which a variable will be incremented. */
    @Getter private UnsignedLong maximumStep;

    @Override
    public void init(final ModifierProperties properties) {
        this.minimum = Optional.ofNullable(properties.getUnsignedLong("minimum")).orElse(UnsignedLong.ZERO);
        this.maximum = Optional.ofNullable(properties.getUnsignedLong("maximum")).orElse(UnsignedLong.MAX_VALUE);

        this.minimumStep = Optional.ofNullable(properties.getUnsignedLong("minimumStep")).orElse(UnsignedLong.ZERO);
        this.maximumStep = Optional.ofNullable(properties.getUnsignedLong("maximumStep")).orElse(UnsignedLong.ONE);
    }

    @Override
    public Counter64 modify(final Counter64 variable) {
        UnsignedLong currentValue = UnsignedLong.valueOf(variable.toString());
        if (currentValue.compareTo(minimum) < 0 || currentValue.compareTo(maximum) > 0) {
            currentValue = minimum;
        }

        final UnsignedLong step = UnsignedLong.valueOf((long) (Math.random() * maximumStep.minus(minimumStep).longValue())).plus(minimumStep);
        final UnsignedLong newValue = currentValue.plus(step);

        return new Counter64(newValue.longValue());
    }

}


<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/package-info.java<|end_filename|>
/**
 * The {@code Snmpman} is a command-line application that simulates {@code SNMP}-capable devices.
 * <br>
 * It also can be used within Unit tests which require the usage of SNMP devices. You can start and stop all agents
 * with pre- and post unit-test execution methods, which are available in probably every Unit-Test framework.
 * <br>
 * Check the documentation of {@link com.oneandone.snmpman.Snmpman} to get more information on the execution.
 */
package com.oneandone.snmpman;

<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/modifier/ModifierTest.java<|end_filename|>
package com.oneandone.snmpman.configuration.modifier;

import com.oneandone.snmpman.configuration.type.ModifierProperties;
import org.snmp4j.smi.Counter32;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.Variable;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

public class ModifierTest {

    @Test
    public void testIsApplicable() {
        final Modifier modifier = new Modifier(".1.3.6.*", "com.oneandone.snmpman.configuration.modifier.Counter32Modifier", new ModifierProperties());
        assertTrue(modifier.isApplicable(new OID(".1.3.6.1")));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testModify() {
        final ModifierProperties modifierProperties = new ModifierProperties();
        modifierProperties.put("minimum", 0L);
        modifierProperties.put("maximum", 3000L);
        modifierProperties.put("minimumStep", 1L);
        modifierProperties.put("maximumStep", 10L);

        final Modifier modifier = new Modifier(".1.3.6.*", "com.oneandone.snmpman.configuration.modifier.Counter32Modifier", modifierProperties);

        final Counter32 counter32 = new Counter32(0L);
        assertEquals(counter32.getValue(), 0L);

        final Variable modifiedVariable = modifier.modify(counter32);
        assertTrue(modifiedVariable instanceof Counter32);
        assertNotEquals(((Counter32) modifiedVariable).getValue(), 0L);
    }
}

<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/AgentConfigurationTest.java<|end_filename|>
package com.oneandone.snmpman.configuration;

import org.testng.annotations.Test;

import java.io.File;

import static org.testng.Assert.assertEquals;

public class AgentConfigurationTest {

    @Test
    public void testInitialization() {
        final AgentConfiguration configuration = new AgentConfiguration(
                "Test", 
                new File("src/test/resources/configuration/cisco.yaml"), 
                new File("src/test/resources/configuration/example.txt"), 
                "127.0.0.1", 8080, 
                "secret");
        
        assertEquals(configuration.getName(), "Test");
        assertEquals(configuration.getWalk(), new File("src/test/resources/configuration/example.txt"));
        assertEquals(configuration.getCommunity(), "secret");
        assertEquals(configuration.getDevice().getName(), "Cisco");
        assertEquals(configuration.getAddress().toByteArray(), new byte[]{ 127, 0, 0, 1 });
    }

    @Test
    public void testInitializationWithoutName() {
        final AgentConfiguration configuration = new AgentConfiguration(
                null,
                new File("src/test/resources/configuration/cisco.yaml"),
                new File("src/test/resources/configuration/example.txt"),
                "127.0.0.1", 8080,
                "secret");

        assertEquals(configuration.getName(), "127.0.0.1:8080");
        assertEquals(configuration.getWalk(), new File("src/test/resources/configuration/example.txt"));
        assertEquals(configuration.getCommunity(), "secret");
        assertEquals(configuration.getDevice().getName(), "Cisco");
        assertEquals(configuration.getAddress().toByteArray(), new byte[]{ 127, 0, 0, 1 });
    }

    @Test
    public void testInitializationWithoutDevice() {
        final AgentConfiguration configuration = new AgentConfiguration(
                "Test",
                null,
                new File("src/test/resources/configuration/example.txt"),
                "127.0.0.1", 8080,
                "secret");

        assertEquals(configuration.getName(), "Test");
        assertEquals(configuration.getWalk(), new File("src/test/resources/configuration/example.txt"));
        assertEquals(configuration.getCommunity(), "secret");
        assertEquals(configuration.getDevice(), AgentConfiguration.DeviceFactory.DEFAULT_DEVICE);
        assertEquals(configuration.getAddress().toByteArray(), new byte[]{ 127, 0, 0, 1 });
    }
}

<|start_filename|>snmpman-cli/src/test/java/com/oneandone/snmpman/CommandLineOptionsTest.java<|end_filename|>
package com.oneandone.snmpman;

import org.kohsuke.args4j.CmdLineParser;
import org.testng.annotations.Test;

import static org.testng.Assert.*;

public class CommandLineOptionsTest {

    @Test
    public void testGetAgents() throws Exception {
        final CommandLineOptions commandLineOptions = new CommandLineOptions();
        final CmdLineParser cmdLineParser = new CmdLineParser(commandLineOptions);
        cmdLineParser.parseArgument("-c", "src/test/resources/configuration/configuration.yaml");
        
        assertEquals(commandLineOptions.getConfigurationFile().getName(), "configuration.yaml");
        assertTrue(commandLineOptions.getConfigurationFile().exists());
        assertTrue(commandLineOptions.getConfigurationFile().isFile());
    }

    @Test
    public void testHelp() throws Exception {
        final CommandLineOptions commandLineOptions = new CommandLineOptions();
        final CmdLineParser cmdLineParser = new CmdLineParser(commandLineOptions);
        cmdLineParser.parseArgument("-h");

        assertTrue(commandLineOptions.isShowHelp());
    }
}

<|start_filename|>snmpman/src/test/java/com/oneandone/snmpman/configuration/DeviceFactoryTest.java<|end_filename|>
package com.oneandone.snmpman.configuration;

import org.testng.annotations.Test;

import java.io.File;

import static org.testng.Assert.*;

public class DeviceFactoryTest {

    @Test
    public void testGetDeviceForNullPath() {
        final AgentConfiguration.DeviceFactory deviceFactory = new AgentConfiguration.DeviceFactory();
        assertEquals(deviceFactory.getDevice(null), AgentConfiguration.DeviceFactory.DEFAULT_DEVICE);
    }

    @Test
    public void testGetDeviceForNonExistentPath() {
        final AgentConfiguration.DeviceFactory deviceFactory = new AgentConfiguration.DeviceFactory();
        assertEquals(deviceFactory.getDevice(new File("src/foo/bar/what.txt")), AgentConfiguration.DeviceFactory.DEFAULT_DEVICE);
    }

    @Test
    public void testGetCiscoDevice() {
        final AgentConfiguration.DeviceFactory deviceFactory = new AgentConfiguration.DeviceFactory();
        final Device ciscoDevice = deviceFactory.getDevice(new File("src/test/resources/configuration/cisco.yaml"));

        assertNotEquals(ciscoDevice, AgentConfiguration.DeviceFactory.DEFAULT_DEVICE);

        assertEquals(ciscoDevice.getName(), "Cisco");
        assertEquals(ciscoDevice.toString(), "Device(name=Cisco)");
        assertFalse(ciscoDevice.getModifiers().isEmpty());
    }

}

<|start_filename|>snmpman/src/main/java/com/oneandone/snmpman/exception/InitializationException.java<|end_filename|>
package com.oneandone.snmpman.exception;

/** Exception type defines the failed initialization of the application. */
public class InitializationException extends RuntimeException {

    /** The version number of this class. */
    private static final long serialVersionUID = 1L;

    /**
     * Constructs a new exception.
     *
     * @param message the detail message
     */
    public InitializationException(final String message) {
        super(message);
    }

    /**
     * Constructs a new exception.
     *
     * @param message the detail message
     * @param cause the exception cause
     */
    public InitializationException(final String message, final Throwable cause) {
        super(message, cause);
    }

}
