/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.CommandLine;
using System.IO;
using Microsoft.Extensions.Logging;
namespace Opc.Ua.Fuzzing
{
public static class Program
{
///
/// Default artifact root, independent of the caller's working directory.
///
public static readonly string RootFolder = AppContext.BaseDirectory;
public static readonly string DefaultTestcasesFolder =
Path.Combine(RootFolder, "Testcases");
public static readonly string DefaultFindingsCrashFolder = RootFolder + "findings/crashes/";
public static readonly string DefaultFindingsHangsFolder = RootFolder + "findings/hangs/";
public static readonly string DefaultLibFuzzerCrashes = RootFolder + "crash-*";
public static readonly string DefaultLibFuzzerHangs = RootFolder + "timeout-*";
public static int Main(string[] args)
{
string applicationName = typeof(Program).Assembly.GetName().Name!;
Console.WriteLine($"OPC UA {applicationName}");
var playbackOption = new Option("--playback", "-p")
{
Description = "playback crashes found by afl-fuzz and libfuzzer"
};
var testcasesOption = new Option("--testcases", "-t")
{
Description = "create test cases for fuzzing"
};
var stacktraceOption = new Option("--stacktrace", "-s")
{
Description = "show stacktrace with playback"
};
var outputOption = new Option("--output")
{
Description = "Testcases path prefix for generation"
};
var inputOption = new Option("--input")
{
Description = "Required replay directory, file or glob"
};
var targetOption = new Option("--target")
{
Description = "Replay only this named target"
};
var rootCommand = new RootCommand($"Usage: {applicationName}.exe [OPTIONS]")
{
playbackOption,
testcasesOption,
stacktraceOption,
outputOption,
inputOption,
targetOption
};
rootCommand.SetAction((parseResult) =>
{
bool playback = parseResult.GetValue(playbackOption);
bool testcases = parseResult.GetValue(testcasesOption);
bool stacktrace = parseResult.GetValue(stacktraceOption);
if (playback == testcases)
{
Console.Error.WriteLine("Specify exactly one of --testcases or --playback.");
return 2;
}
if (playback && string.IsNullOrEmpty(parseResult.GetValue(inputOption)))
{
Console.Error.WriteLine("--playback requires --input.");
return 2;
}
if ((playback && parseResult.GetValue(outputOption) != null) ||
(testcases && (parseResult.GetValue(inputOption) != null ||
parseResult.GetValue(targetOption) != null)))
{
Console.Error.WriteLine("--output is for generation; --input and --target are for playback.");
return 2;
}
var telemetry = new Logging();
telemetry.Configure(applicationName!, string.Empty, true, LogLevel.Trace);
if (testcases)
{
Testcases.Run(
Path.GetFullPath(parseResult.GetValue(outputOption) ?? DefaultTestcasesFolder),
telemetry);
}
else
{
Playback.Run(
parseResult.GetValue(inputOption)!,
stacktrace,
telemetry,
parseResult.GetValue(targetOption)!);
}
return 0;
});
ParseResult parseResult = rootCommand.Parse(args);
return parseResult.Invoke(new InvocationConfiguration());
}
}
}