/* ========================================================================
* 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.Globalization;
using Opc.Ua;
namespace ConsoleDataChannelStreaming
{
///
/// Which framing the sample exercises.
///
internal enum SampleTransport
{
///
/// Inline framing: STR MessageChunks interleaved with Service
/// traffic on one connection, with credit-based flow control.
///
Tcp,
///
/// opc.quic: the channel is bound to its own QUIC stream and QUIC
/// applies the flow control.
///
Quic
}
internal enum SampleRunMode
{
Server,
Direct,
Benchmark
}
///
/// The command line of the sample.
///
internal sealed record SampleOptions
{
///
/// Which framing to exercise.
///
public SampleTransport Transport { get; init; } = SampleTransport.Tcp;
public SampleRunMode RunMode { get; init; } = SampleRunMode.Server;
///
/// How many monitored items the benchmark subscribes to, which is
/// what sets the size of the competing Publish load.
///
public int MonitoredItems { get; init; } = 100;
///
/// How many measured runs the benchmark takes per case, after one
/// warm-up run that is discarded.
///
public int Repeat { get; init; } = 3;
///
/// How many frames to send.
///
public int FrameCount { get; init; } = 300;
///
/// The payload size of each frame.
///
public int FrameSize { get; init; } = 1200;
///
/// The delivery guarantee to negotiate.
///
public DataChannelDeliveryMode DeliveryMode { get; init; }
= DataChannelDeliveryMode.ReliableOrdered;
///
/// True when the user asked for usage.
///
public bool ShowHelp { get; init; }
///
/// Parses the command line.
///
/// The arguments.
public static SampleOptions Parse(string[] args)
{
var options = new SampleOptions();
for (int ii = 0; ii < args.Length; ii++)
{
string argument = args[ii];
switch (argument)
{
case "-h":
case "--help":
return options with { ShowHelp = true };
case "--transport":
options = options with
{
Transport = Next(args, ref ii)
.Equals("quic", StringComparison.OrdinalIgnoreCase)
? SampleTransport.Quic
: SampleTransport.Tcp
};
break;
case "--frames":
options = options with { FrameCount = ParseInt(Next(args, ref ii)) };
break;
case "--size":
options = options with { FrameSize = ParseInt(Next(args, ref ii)) };
break;
case "--mode":
string mode = Next(args, ref ii);
if (mode.Equals("direct", StringComparison.OrdinalIgnoreCase))
{
options = options with { RunMode = SampleRunMode.Direct };
}
else if (mode.Equals("server", StringComparison.OrdinalIgnoreCase))
{
options = options with { RunMode = SampleRunMode.Server };
}
else if (mode.Equals("benchmark", StringComparison.OrdinalIgnoreCase))
{
options = options with { RunMode = SampleRunMode.Benchmark };
}
else
{
options = options with { DeliveryMode = ParseDeliveryMode(mode) };
}
break;
case "--monitored-items":
options = options with { MonitoredItems = ParseInt(Next(args, ref ii)) };
break;
case "--repeat":
options = options with { Repeat = ParseInt(Next(args, ref ii)) };
break;
case "--delivery":
options = options with { DeliveryMode = ParseDeliveryMode(Next(args, ref ii)) };
break;
default:
Console.Error.WriteLine($"unknown argument '{argument}'");
return options with { ShowHelp = true };
}
}
return options;
}
///
/// Prints the usage banner.
///
public static void PrintUsage()
{
Console.WriteLine("usage: ConsoleDataChannelStreaming [options]");
Console.WriteLine();
Console.WriteLine(" --transport tcp|quic framing to exercise (default tcp)");
Console.WriteLine(" --mode server|direct|benchmark");
Console.WriteLine(" real Session/OpenDataChannel, direct manager,");
Console.WriteLine(" or the throughput matrix (default server)");
Console.WriteLine(" --frames N frames to send (default 300)");
Console.WriteLine(" --size N payload bytes per frame (default 1200,");
Console.WriteLine(" which fits one QUIC datagram without");
Console.WriteLine(" IP fragmentation)");
Console.WriteLine(" --delivery reliable|reliable-unordered|partial|unreliable");
Console.WriteLine(" --monitored-items N benchmark: items generating the competing");
Console.WriteLine(" Publish load (default 100)");
Console.WriteLine(" --repeat N benchmark: measured runs per case after one");
Console.WriteLine(" discarded warm-up (default 3)");
Console.WriteLine(" -h, --help this text");
}
private static string Next(string[] args, ref int index)
{
index++;
return index < args.Length ? args[index] : string.Empty;
}
private static int ParseInt(string value)
{
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed)
? parsed
: 0;
}
private static DataChannelDeliveryMode ParseDeliveryMode(string value)
{
switch (value.ToLowerInvariant())
{
case "reliable-unordered":
return DataChannelDeliveryMode.ReliableUnordered;
case "partial":
return DataChannelDeliveryMode.PartiallyReliable;
case "unreliable":
return DataChannelDeliveryMode.Unreliable;
default:
return DataChannelDeliveryMode.ReliableOrdered;
}
}
}
}