OpenRaoFuzzer.java

// Copyright 2025 Google LLC
//
// 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.
//
///////////////////////////////////////////////////////////////////////////
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.powsybl.commons.PowsyblException;
import com.powsybl.contingency.ContingencyElementType;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.NetworkFactory;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.loadflow.LoadFlowParameters;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.CracFactory;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.networkaction.ActionType;
import com.powsybl.openrao.data.crac.api.range.RangeType;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import com.powsybl.openrao.data.crac.io.commons.iidm.IidmPstHelper;
import com.powsybl.openrao.raoapi.Rao;
import com.powsybl.openrao.raoapi.RaoInput;
import com.powsybl.openrao.raoapi.parameters.RaoParameters;
import com.powsybl.sensitivity.SensitivityAnalysisParameters;
import java.util.EnumSet;
import java.util.Properties;

public class OpenRaoFuzzer {
  public static void fuzzerTestOneInput(FuzzedDataProvider data) {
    // String length + 3 Doubles + 2 Integers + 1 Booleans + 8 pick values + bytes for network
    Integer requiredBytes = 10 + (3 * 8) + (2 * 4) + (1 * 1) + (8 * 4) + 1;
    if (data.remainingBytes() < requiredBytes) {
      return;
    }

    try {
      // Randomise variables
      String newString = data.consumeString(10);
      ContingencyElementType newType = data.pickValue(EnumSet.allOf(ContingencyElementType.class));
      InstantKind newInstant = data.pickValue(EnumSet.allOf(InstantKind.class));
      Unit newUnit = data.pickValue(EnumSet.allOf(Unit.class));
      RangeType newRange = data.pickValue(EnumSet.allOf(RangeType.class));
      UsageMethod newUsage = data.pickValue(EnumSet.allOf(UsageMethod.class));
      ActionType newAction = data.pickValue(EnumSet.allOf(ActionType.class));
      TwoSides newSide = data.pickValue(EnumSet.allOf(TwoSides.class));
      double newMin = data.consumeDouble();
      double newMax = data.consumeDouble();
      int minTap = data.consumeInt();
      int maxTap = data.consumeInt();
      boolean isDc = data.consumeBoolean();

      // Initialise properties
      Properties properties = new Properties();
      properties.setProperty("solver", data.pickValue(new String[] {"DEFAULT", "NEWTON", "GAUSS"}));
      properties.setProperty("convergence", String.valueOf(data.consumeDouble()));

      // Initialise objects
      Network network = NetworkFactory.findDefault().createNetwork("Fuzz", "Fuzz");
      Crac crac = CracFactory.findDefault().create("Fuz-Crac");
      IidmPstHelper iidmPstHelper = new IidmPstHelper(newString, network);

      // Randomise Crac object
      crac.newContingency().withId("contingency").withContingencyElement(newString, newType).add();
      crac.newInstant("fuzz-instant", newInstant);

      crac.newFlowCnec()
          .withId("fuzz-flow")
          .withInstant("fuzz-instant")
          .withOptimized()
          .withNetworkElement(newString)
          .newThreshold()
          .withMin(newMin)
          .withMax(newMax)
          .withUnit(newUnit)
          .withSide(newSide)
          .add()
          .add();

      crac.newPstRangeAction()
          .withId("fuzz-action")
          .withNetworkElement(newString)
          .withInitialTap(iidmPstHelper.getInitialTap())
          .withTapToAngleConversionMap(iidmPstHelper.getTapToAngleConversionMap())
          .newTapRange()
          .withMinTap(minTap)
          .withMaxTap(maxTap)
          .withRangeType(newRange)
          .add()
          .newOnInstantUsageRule()
          .withInstant("fuzz-instant")
          .withUsageMethod(newUsage)
          .add()
          .add();

      crac.newNetworkAction()
          .withId("fuzz-network-action")
          .newTerminalsConnectionAction()
          .withNetworkElement(newString)
          .withActionType(newAction)
          .add()
          .add();

      // Set parameters
      RaoParameters raoParameters = new RaoParameters();
      LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
      loadFlowParameters.setDc(isDc);
      SensitivityAnalysisParameters sensitivityAnalysisParameters =
          new SensitivityAnalysisParameters();
      sensitivityAnalysisParameters.setLoadFlowParameters(loadFlowParameters);

      RaoInput.RaoInputBuilder raoInputBuilder = RaoInput.build(network, crac);
      Rao.find().run(raoInputBuilder.build(), raoParameters);
    } catch (PowsyblException
        | OpenRaoException
        | IllegalArgumentException
        | IllegalStateException e) {
      // Ignore known exceptions
    } catch (NullPointerException e) {
      // Capture known NPE from malformed JSON
      if (!isExpected(e)) {
        throw e;
      }
    }
  }

  private static boolean isExpected(Throwable e) {
    String[] expectedString = {
      "java.util.Objects.requireNonNull",
      "Cannot invoke \"String.hashCode()\"",
      "Name is null",
      "Cannot invoke \"com.fasterxml.jackson.databind.JsonNode.get(String)\""
    };

    for (String expected : expectedString) {
      if (e.toString().contains(expected)) {
        return true;
      }
      for (StackTraceElement ste : e.getStackTrace()) {
        if (ste.toString().contains(expected)) {
          return true;
        }
      }
    }

    return false;
  }
}