/* ========================================================================
* Copyright (c) 2005-2026 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.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
namespace Opc.Ua.Fuzzing
{
///
/// Supplies observable callbacks and deliberately invalid signatures for the linked harness.
///
public sealed class FuzzableCode
{
internal static List<(string Target, byte[] Input)> Invocations { get; } = [];
internal static int FuzzInfoCalls { get; private set; }
internal static Exception InjectedFailure { get; set; } =
new InvalidOperationException("Injected fuzz target failure.");
internal static ReadOnlySpan HangingInput => "opcua-fuzz-watchdog-regression"u8;
internal static ReadOnlySpan CompletedInput => "opcua-fuzz-watchdog-control"u8;
internal static ReadOnlySpan LargeOutputInput => "opcua-fuzz-output-control"u8;
internal static ReadOnlySpan DeepRecursionInput => "opcua-fuzz-deep-recursion"u8;
internal static string[] InvalidTargetNames =>
[
nameof(FuzzInfo),
nameof(ReturningStreamTarget),
nameof(ReturningStringTarget),
nameof(ReturningSpanTarget),
nameof(GenericStreamTarget),
nameof(InstanceStreamTarget),
nameof(MultipleParametersTarget),
nameof(ByteArrayTarget),
nameof(WritableSpanTarget),
nameof(DerivedStreamTarget),
nameof(ByRefStringTarget),
nameof(HiddenStreamTarget),
nameof(InternalStreamTarget)
];
public static void FuzzInfo()
{
FuzzInfoCalls++;
}
public static void StreamTarget(Stream input)
{
RecordStream(nameof(StreamTarget), input);
}
public static void StringTarget(string input)
{
Invocations.Add((nameof(StringTarget), Encoding.UTF8.GetBytes(input)));
}
public static void SpanTarget(ReadOnlySpan input)
{
Invocations.Add((nameof(SpanTarget), input.ToArray()));
}
public static void ThrowingStreamTarget(Stream input)
{
RecordStream(nameof(ThrowingStreamTarget), input);
throw InjectedFailure;
}
public static void ThrowingStringTarget(string input)
{
Invocations.Add((nameof(ThrowingStringTarget), Encoding.UTF8.GetBytes(input)));
throw InjectedFailure;
}
public static void ThrowingSpanTarget(ReadOnlySpan input)
{
Invocations.Add((nameof(ThrowingSpanTarget), input.ToArray()));
throw InjectedFailure;
}
public static void HangingSpanTarget(ReadOnlySpan input)
{
Invocations.Add((nameof(HangingSpanTarget), input.ToArray()));
if (input.SequenceEqual(HangingInput))
{
Console.WriteLine(HangingTargetEntered);
Console.Out.Flush();
Thread.Sleep(Timeout.Infinite);
}
else if (input.SequenceEqual(CompletedInput))
{
Console.WriteLine(TargetCompleted);
}
else if (input.SequenceEqual(LargeOutputInput))
{
Console.Out.Write(new string('o', OutputLength));
Console.Error.Write(new string('e', OutputLength));
}
}
///
/// Recurses about 400 KB deep for : within the stack of a
/// 1 MB thread, beyond the stack of the fuzz worker thread.
///
public static void DeepRecursionSpanTarget(ReadOnlySpan input)
{
Invocations.Add((nameof(DeepRecursionSpanTarget), input.ToArray()));
if (input.SequenceEqual(DeepRecursionInput))
{
Console.WriteLine(TargetCompleted + " " + Recurse(DeepRecursionLevels));
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static int Recurse(int levels)
{
Span frame = stackalloc byte[RecursionFrameBytes];
frame[levels % RecursionFrameBytes] = (byte)levels;
return levels == 0 ? frame[0] : Recurse(levels - 1) + frame[levels % RecursionFrameBytes];
}
public static int ReturningStreamTarget(Stream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static string ReturningStringTarget(string input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static int ReturningSpanTarget(ReadOnlySpan input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void GenericStreamTarget(Stream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public void InstanceStreamTarget(Stream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void MultipleParametersTarget(Stream input, string text)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void ByteArrayTarget(byte[] input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void WritableSpanTarget(Span input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void DerivedStreamTarget(MemoryStream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
public static void ByRefStringTarget(ref string input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
internal static void Reset()
{
Invocations.Clear();
FuzzInfoCalls = 0;
InjectedFailure = new InvalidOperationException("Injected fuzz target failure.");
}
internal static void InternalStreamTarget(Stream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
private static void HiddenStreamTarget(Stream input)
{
throw new InvalidOperationException(kInvalidTargetMessage);
}
private static void RecordStream(string target, Stream input)
{
using var copy = new MemoryStream();
input.CopyTo(copy);
Invocations.Add((target, copy.ToArray()));
}
internal const string HangingTargetEntered = "Injected hanging target entered.";
internal const string TargetCompleted = "Injected target completed.";
internal const int OutputLength = 128 * 1024;
internal const int DeepRecursionLevels = 400;
internal const int RecursionFrameBytes = 1024;
private const string kInvalidTargetMessage = "An invalid fuzz target must not be invoked.";
}
}