content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.apache.camel.quarkus.component.snmp.it;
  
  import java.io.IOException;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Vector;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.stream.Collectors;
  
  import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
  import org.apache.camel.quarkus.test.AvailablePortFinder;
  import org.apache.camel.util.CollectionHelper;
  import org.junit.jupiter.api.Assertions;
  import org.snmp4j.CommandResponder;
  import org.snmp4j.CommandResponderEvent;
  import org.snmp4j.MessageException;
  import org.snmp4j.PDU;
  import org.snmp4j.PDUv1;
  import org.snmp4j.ScopedPDU;
  import org.snmp4j.Snmp;
  import org.snmp4j.mp.CounterSupport;
  import org.snmp4j.mp.DefaultCounterListener;
  import org.snmp4j.mp.MPv3;
  import org.snmp4j.mp.SnmpConstants;
  import org.snmp4j.mp.StatusInformation;
  import org.snmp4j.security.AuthSHA;
  import org.snmp4j.security.SecurityModels;
  import org.snmp4j.security.SecurityProtocols;
  import org.snmp4j.security.USM;
  import org.snmp4j.security.UsmUser;
  import org.snmp4j.smi.OID;
  import org.snmp4j.smi.OctetString;
  import org.snmp4j.smi.UdpAddress;
  import org.snmp4j.smi.VariableBinding;
  import org.snmp4j.transport.DefaultUdpTransportMapping;
  
  public class SnmpTestResource implements QuarkusTestResourceLifecycleManager {
  
      public static final String LISTEN_ADDRESS = "snmpListenAddress";
      public static final String LOCAL_ADDRESS = "127.0.0.1/0";
  
      Snmp snmpResponder;
  
      @Override
      public Map<String, String> start() {
          SecurityProtocols.getInstance().addDefaultProtocols();
          DefaultUdpTransportMapping udpTransportMapping;
          try {
              udpTransportMapping = new DefaultUdpTransportMapping(new UdpAddress(LOCAL_ADDRESS));
              snmpResponder = new Snmp(udpTransportMapping);
  
              TestCommandResponder responder = new TestCommandResponder(snmpResponder);
              snmpResponder.addCommandResponder(responder);
  
              SecurityModels respSecModels = new SecurityModels() {
              };
  
              CounterSupport.getInstance().addCounterListener(new DefaultCounterListener());
              MPv3 mpv3CR = (MPv3) snmpResponder.getMessageDispatcher().getMessageProcessingModel(MPv3.ID);
              mpv3CR.setLocalEngineID(MPv3.createLocalEngineID(new OctetString("responder")));
              respSecModels.addSecurityModel(new USM(SecurityProtocols.getInstance(),
                      new OctetString(mpv3CR.getLocalEngineID()), 0));
              mpv3CR.setSecurityModels(respSecModels);
  
              snmpResponder.getUSM().addUser(
                      new UsmUser(new OctetString("test"), AuthSHA.ID, new OctetString("changeit"),
                              AuthSHA.ID, new OctetString("changeit")));
  
              snmpResponder.listen();
          } catch (Exception e) {
              throw new RuntimeException(e);
          }
  
          Map<String, String> ports = AvailablePortFinder.reserveNetworkPorts(Objects::toString, SnmpRoute.TRAP_V0_PORT,
                  SnmpRoute.TRAP_V1_PORT, SnmpRoute.TRAP_V3_PORT);
          Map<String, String> m = CollectionHelper.mergeMaps(
                  ports,
                  CollectionHelper.mapOf(LISTEN_ADDRESS,
                          udpTransportMapping.getListenAddress().toString().replaceFirst("/", ":")));
          return m;
      }
  
      @Override
      public void stop() {
          SecurityProtocols.setSecurityProtocols(null);
          if (snmpResponder != null) {
              try {
                  snmpResponder.close();
              } catch (IOException e) {
                  //do nothing
              }
          }
      }
  
      static class TestCommandResponder implements CommandResponder {
  
          private final Snmp commandResponder;
          private final Map<String, Integer> counts = new ConcurrentHashMap<>();
  
          public TestCommandResponder(Snmp commandResponder) {
              this.commandResponder = commandResponder;
          }
  
          @Override
          public synchronized void processPdu(CommandResponderEvent event) {
              PDU pdu = event.getPDU();
              Vector<? extends VariableBinding> vbs;
              if (pdu.getVariableBindings() != null) {
                  vbs = new Vector<>(pdu.getVariableBindings());
              } else {
                  vbs = new Vector<>(0);
              }
              String key = vbs.stream().sequential().map(vb -> vb.getOid().toString()).collect(Collectors.joining(","));
  
              //differ snmp versions
              if (pdu instanceof PDUv1) {
                  key = "v1_" + key;
              } else if (pdu instanceof ScopedPDU) {
                  key = "v3_" + key;
              } else {
                  key = "v2_" + key;
              }
              int numberOfSent = counts.getOrDefault(key, 0);
  
              try {
                  PDU response = makeResponse(pdu, ++numberOfSent, SnmpConstants.version1, vbs);
                  if (response != null) {
                      response.setRequestID(pdu.getRequestID());
                      commandResponder.getMessageDispatcher().returnResponsePdu(
                              event.getMessageProcessingModel(), event.getSecurityModel(),
                              event.getSecurityName(), event.getSecurityLevel(),
                              response, event.getMaxSizeResponsePDU(),
                              event.getStateReference(), new StatusInformation());
                  }
              } catch (MessageException e) {
                  Assertions.assertNull(e);
              }
              counts.put(key, numberOfSent);
          }
  
          private PDU makeResponse(PDU originalPDU, int counter, int version, Vector<? extends VariableBinding> vbs) {
              PDU responsePDU = (PDU) originalPDU.clone();
              responsePDU.setType(PDU.RESPONSE);
              responsePDU.setErrorStatus(PDU.noError);
              responsePDU.setErrorIndex(0);
              if (vbs.isEmpty()) {
                  VariableBinding vb = generateResponseBinding(counter, version, SnmpTest.PRODUCE_PDU_OID);
                  if (vb != null) {
                      responsePDU.add(vb);
                  }
              } else {
                  vbs.stream().forEach(vb -> responsePDU.add(generateResponseBinding(counter, version, vb.getOid())));
              }
              if (responsePDU.getVariableBindings().isEmpty()) {
                  return null;
              }
              return responsePDU;
          }
  
          private VariableBinding generateResponseBinding(int counter, int version, OID oid) {
              //get next test
              if (SnmpTest.GET_NEXT_OID.equals(oid)) {
                  //if counter < 2 return the same oid
                  if (counter < 3) {
                      return new VariableBinding(SnmpTest.GET_NEXT_OID, new OctetString("" + counter));
                  }
                  if (counter == 3) {
                      //else return sysDescr
                      return new VariableBinding(SnmpTest.GET_NEXT_OID,
                              new OctetString("My GET_NEXT Printer - response #" + counter));
                  }
                  //else do not send response
                  return null;
              }
  
              if (SnmpTest.POLL_OID.equals(oid)) {
                  if (counter > 1) {
                      throw new RuntimeException(
                              String.format("Not expected request #%d for poll of version %d.", counter, version));
                  }
                  return new VariableBinding(SnmpTest.POLL_OID,
                          new OctetString("My POLL Printer - response #" + counter));
              }
  
              if (SnmpTest.PRODUCE_PDU_OID.equals(oid)) {
                  if (counter < 4) {
                      return new VariableBinding(SnmpTest.PRODUCE_PDU_OID,
                              new OctetString("My PRODUCE_PDU Printer - response #" + counter));
                  }
              }
  
              if (SnmpTest.TWO_OIDS_A.equals(oid)) {
                  if (counter < 4) {
                      return new VariableBinding(SnmpTest.TWO_OIDS_A,
                              new OctetString("My 2 OIDs A Printer - response #" + counter));
                  }
              }
  
              if (SnmpTest.TWO_OIDS_B.equals(oid)) {
                  if (counter < 4) {
                      return new VariableBinding(SnmpTest.TWO_OIDS_B,
                              new OctetString("My 2 OIDs B Printer - response #" + counter));
                  }
              }
  
              if (SnmpTest.DOT_OID.equals(oid)) {
                  return new VariableBinding(SnmpTest.DOT_OID,
                          new OctetString("My DOT Printer - response #" + counter));
              }
  
              return null;
          }
      }
  }
  