/*_############################################################################
  _## 
  _##  SNMP4J-Agent 3 - SampleAgentTest.java  
  _## 
  _##  Copyright (C) 2005-2025  Frank Fock (SNMP4J.org)
  _##  
  _##  Licensed under the Apache License, Version 2.0 (the "License");
  _##  you may not use this file except in compliance with the License.
  _##  You may obtain a copy of the License at
  _##  
  _##      http://www.apache.org/licenses/LICENSE-2.0
  _##  
  _##  Unless required by applicable law or agreed to in writing, software
  _##  distributed under the License is distributed on an "AS IS" BASIS,
  _##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  _##  See the License for the specific language governing permissions and
  _##  limitations under the License.
  _##  
  _##########################################################################*/
package org.snmp4j.agent.example;

import org.junit.Test;
import org.snmp4j.*;
import org.snmp4j.agent.DefaultMOServer;
import org.snmp4j.agent.MOQuery;
import org.snmp4j.agent.MOServer;
import org.snmp4j.agent.ManagedObject;
import org.snmp4j.agent.audit.AgentAuditLevel;
import org.snmp4j.agent.audit.AgentPolicyProfile;
import org.snmp4j.agent.audit.DefaultAgentAuditPolicy;
import org.snmp4j.agent.io.ImportMode;
import org.snmp4j.agent.mo.lock.MOLockStrategy;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.log.ConsoleLogAdapter;
import org.snmp4j.log.ConsoleLogFactory;
import org.snmp4j.log.LogFactory;
import org.snmp4j.log.LogLevel;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.ArgumentParser;
import org.snmp4j.util.DefaultPDUFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import static org.junit.Assert.*;

public class SampleAgentTest {

    public static final String PRIV_SHA256AES128PrivPassword = "SHA256AES128PrivPassword";
    public static final String SHA_256_AES_128 = "SHA256AES128";

    static {
        SNMP4JSettings.setSecretLoggingEnabled(true);
    }
    static {
        LogFactory.setLogFactory(new ConsoleLogFactory());
        ConsoleLogAdapter.setWarnEnabled(true);
    }

    private static final String[] ARGS =
            {"-c", "-c SampleAgentTest.cfg", "-bc", "SampleAgentTestBC.cfg", "-admin", PRIV_SHA256AES128PrivPassword,
                    "udp:127.0.0.1/0"};

    private MOServer server;
    private byte[] localEngineID;


    private SampleAgent sampleAgent;

    @org.junit.Before
    public void setUp() throws Exception {
        SecurityProtocols.getInstance().addDefaultProtocols();
        server = new DefaultMOServer();
        MOServer[] moServers = new MOServer[]{server};
        ArgumentParser parser = new ArgumentParser(SampleAgent.COMMAND_LINE_OPTIONS, SampleAgent.COMMAND_LINE_PARAMS);
        Map<String, List<Object>> commandLineParameters;
        commandLineParameters = parser.parse(ARGS);

        Properties tableSizeLimits = SampleAgent.getTableSizeLimitsProperties(commandLineParameters);

        sampleAgent =
                new SampleAgent(commandLineParameters, moServers, null,
                        tableSizeLimits, ImportMode.replaceCreate,
                        new DefaultAgentAuditPolicy(new AgentPolicyProfile(AgentPolicyProfile.LOCALHOST,
                                new String[] { "SampleAgentTest.cfg", "SampleAgentTestBC.cfg"},
                                null, null, null),
                                AgentAuditLevel.RESTRICT));
        //LogFactory.setLogFactory(new ConsoleLogFactory());
        LogFactory.getLogFactory().getRootLogger().setLogLevel(LogLevel.WARN);

        ((DefaultMOServer) server).setLockStrategy(new MOLockStrategy() {
            @Override
            public boolean isLockNeeded(ManagedObject<?> managedObjectLookedUp, MOQuery query) {
                return true;
            }
        });
    }

    @org.junit.After
    public void tearDown() throws Exception {
        sampleAgent.agent.shutdown();
    }


    @Test
    public void testGetBulkRepetitions() throws IOException {
        try {
            Files.deleteIfExists(new File("SampleAgentTest.cfg").toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        sampleAgent.run();

        Snmp clientSession = new Snmp(new DefaultUdpTransportMapping(new UdpAddress()));
        clientSession.listen();
        Target<UdpAddress> target = getAgentTargetAddress();
        target.setVersion(SnmpConstants.version2c);
        target.setTimeout(10000);
        PDU bulkPDU = new DefaultPDUFactory(PDU.GETBULK).createPDU(target);
        bulkPDU.setMaxRepetitions(Integer.MAX_VALUE);
        bulkPDU.add(new VariableBinding(new OID("1.3")));
        ResponseEvent<UdpAddress> responseEvent = clientSession.send(bulkPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertTrue(11 <= responseEvent.getResponse().size());

        bulkPDU.setMaxRepetitions(Short.MAX_VALUE);
        bulkPDU.add(new VariableBinding(new OID("1.3")));
        responseEvent = clientSession.send(bulkPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertTrue(11 <= responseEvent.getResponse().size());

        bulkPDU.setMaxRepetitions(0);
        bulkPDU.setNonRepeaters(2);
        bulkPDU.add(new VariableBinding(new OID("1.3")));
        bulkPDU.add(new VariableBinding(new OID("1.3")));
        bulkPDU.add(new VariableBinding(new OID("1.3")));
        responseEvent = clientSession.send(bulkPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertEquals(2, responseEvent.getResponse().size());

        bulkPDU.setMaxRepetitions(Integer.MIN_VALUE);
        bulkPDU.setNonRepeaters(0);
        responseEvent = clientSession.send(bulkPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertEquals(0, responseEvent.getResponse().size());
        clientSession.close();
    }

    private Target<UdpAddress> getAgentTargetAddress() {
        UdpAddress agentListenAddress = (UdpAddress)
                sampleAgent.agent.getMessageDispatcher().getTransportMappings().iterator().next().getListenAddress();
        return new CommunityTarget<>(agentListenAddress, new OctetString("public"));
    }

    @Test
    public void testGetWithReordering() throws IOException {
        try {
            Files.deleteIfExists(new File("SampleAgentTest.cfg").toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        sampleAgent.run();

        sampleAgent.agent.getCommandProcessor().setLockNonNextRequestsSortedByVbOid(true);

        Snmp clientSession = new Snmp(new DefaultUdpTransportMapping(new UdpAddress()));
        clientSession.listen();
        Target<UdpAddress> target = getAgentTargetAddress();
        target.setVersion(SnmpConstants.version2c);
        target.setTimeout(10000);
        PDU getPDU = new DefaultPDUFactory(PDU.GET).createPDU(target);
        getPDU.add(new VariableBinding(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.2.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.2.0")));
        ResponseEvent<UdpAddress> responseEvent = clientSession.send(getPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertEquals(getPDU.getVariableBindings().size(), responseEvent.getResponse().size());
        assertEquals(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0"), responseEvent.getResponse().get(0).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.2.0"), responseEvent.getResponse().get(1).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.1.0"), responseEvent.getResponse().get(2).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.2.0"), responseEvent.getResponse().get(3).getOid());
        clientSession.close();
    }

    @Test
    public void testGetWithReorderAndNoSuchObjectAndInstance() throws IOException {
        try {
            Files.deleteIfExists(new File("SampleAgentTest.cfg").toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        sampleAgent.run();

        Snmp clientSession = new Snmp(new DefaultUdpTransportMapping(new UdpAddress()));
        clientSession.listen();
        Target<UdpAddress> target = getAgentTargetAddress();
        target.setVersion(SnmpConstants.version2c);
        target.setTimeout(10000);
        PDU getPDU = new DefaultPDUFactory(PDU.GET).createPDU(target);
        getPDU.add(new VariableBinding(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.2.0.1")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.10.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1.0")));
        ResponseEvent<UdpAddress> responseEvent = clientSession.send(getPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertEquals(getPDU.size(), responseEvent.getResponse().size());
        assertEquals(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0"), responseEvent.getResponse().get(0).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.2.0.1"), responseEvent.getResponse().get(1).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.10.0"), responseEvent.getResponse().get(2).getOid());
        assertEquals(Null.noSuchInstance, responseEvent.getResponse().get(1).getVariable());
        assertEquals(Null.noSuchObject, responseEvent.getResponse().get(2).getVariable());
        assertEquals(new OID("1.3.6.1.2.1.1.1.0"), responseEvent.getResponse().get(3).getOid());
        clientSession.close();
    }

    @Test
    public void testDirectUserWithDeferredLocalization() throws IOException {
        try {
            Files.deleteIfExists(new File("SampleAgentTest.cfg").toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        sampleAgent.run();
        Snmp clientSession = new Snmp(new DefaultUdpTransportMapping(new UdpAddress()));
        MPv3 mpv3 = (MPv3) clientSession.getMessageProcessingModel(3);
        USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID(new OctetString("1234"))), 0);
        SecurityModels securityModels = new SecurityModels();
        securityModels.addSecurityModel(usm);
        mpv3.setSecurityModels(securityModels);
        clientSession.getMessageDispatcher().addMessageProcessingModel(mpv3);
        clientSession.listen();
        UdpAddress agentAddress = getAgentTargetAddress().getAddress();
        DirectUserTarget<UdpAddress> target = new DirectUserTarget<>(agentAddress,
                new OctetString(SHA_256_AES_128),
                SecurityProtocols.getInstance().getAuthenticationProtocol(AuthHMAC192SHA256.ID),
                new OctetString(PRIV_SHA256AES128PrivPassword),
                SecurityProtocols.getInstance().getPrivacyProtocol(PrivAES128.ID),
                new OctetString(PRIV_SHA256AES128PrivPassword));
        target.setVersion(SnmpConstants.version3);
        target.setTimeout(10000);
        target.setSecurityLevel(SecurityLevel.AUTH_PRIV);
//        usm.setEngineDiscoveryEnabled(false);
        PDU getPDU = new DefaultPDUFactory(PDU.GET).createPDU(target);
        getPDU.add(new VariableBinding(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.2.0.1")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.10.0")));
        getPDU.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1.0")));
        ResponseEvent<UdpAddress> responseEvent = clientSession.send(getPDU, target);
        assertNotNull(responseEvent);
        assertNotNull(responseEvent.getResponse());
        assertEquals(getPDU.size(), responseEvent.getResponse().size());
        assertEquals(new OID("1.3.6.1.4.1.4976.10.1.1.1.1.1.1.0"), responseEvent.getResponse().get(0).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.2.0.1"), responseEvent.getResponse().get(1).getOid());
        assertEquals(new OID("1.3.6.1.2.1.1.10.0"), responseEvent.getResponse().get(2).getOid());
        assertEquals(Null.noSuchInstance, responseEvent.getResponse().get(1).getVariable());
        assertEquals(Null.noSuchObject, responseEvent.getResponse().get(2).getVariable());
        assertEquals(new OID("1.3.6.1.2.1.1.1.0"), responseEvent.getResponse().get(3).getOid());
        clientSession.close();

    }
}