Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion src/samples/InteropTest-gsf/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
using sttp;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Threading;
using System.Xml;

namespace InteropTest;

Expand All @@ -37,6 +40,12 @@ class Program
private static ulong s_processCount;
private static bool s_ready;

// Metadata interop-mode state
private static readonly ManualResetEventSlim s_metadataComplete = new(false);
private static volatile bool s_metadataRequested;
private static volatile bool s_metadataHandled;
private static int s_metadataExitCode = 2; // default: timeout / no metadata received

private static void StatusMessage(string message)
{
// Calls can come from multiple threads, so we impose a simple lock before write to console
Expand All @@ -58,7 +67,7 @@ static void Main(string[] args)
if (args.Length < 2)
{
Console.WriteLine("Usage:");
Console.WriteLine(" InteropTest HOSTNAME PORT");
Console.WriteLine(" InteropTest HOSTNAME PORT [--metadata [--gzip]]");
return;
}

Expand All @@ -71,6 +80,18 @@ static void Main(string[] args)
return;
}

// Metadata interop mode: request XML metadata, dump its datetime values, and exit with a
// status code (0 = metadata received and parsed, non-zero = deserialization error / timeout).
// This exercises the publisher's xs:dateTime serialization against the .NET DataSet parser.
// Add --gzip to advertise the GZip compression mode (compressed-metadata path); without it
// the .NET-default modes are used (CompressMetadata without GZip -> uncompressed path).
if (args.Length > 2 && args[2].Equals("--metadata", StringComparison.OrdinalIgnoreCase))
{
bool useGZip = Array.Exists(args, a => a.Equals("--gzip", StringComparison.OrdinalIgnoreCase));
RunMetadataMode(hostname, port, useGZip);
return;
}

// Initialize the subscribers, maintaining the life-time of SubscriberHandler instances within main
DataSubscriber subscriber = new();
subscriber.StatusMessage += (_, e) => StatusMessage(e.Argument);
Expand Down Expand Up @@ -153,4 +174,121 @@ private static void subscriber_ConnectionTerminated(object sender, EventArgs e)
s_export?.Close();
s_export = null;
}

// ----------------------------------------------------------------------------------------------
// Metadata interop mode
// ----------------------------------------------------------------------------------------------

private static void RunMetadataMode(string hostname, ushort port, bool useGZip)
{
DataSubscriber subscriber = new();
subscriber.StatusMessage += (_, e) => StatusMessage(e.Argument);
subscriber.ProcessException += metadata_ProcessException;
subscriber.ConnectionEstablished += metadata_ConnectionEstablished;
subscriber.ConnectionTerminated += (_, _) => StatusMessage("Connection terminated.");
subscriber.MetaDataReceived += metadata_MetaDataReceived;
subscriber.ConnectionString = $"server={hostname}:{port}";

// A correct STTP publisher GZip-compresses metadata only when the subscriber negotiates
// BOTH CompressMetadata (on by default) and the GZip compression mode. With --gzip we
// advertise GZip to exercise the compressed path; without it we use the .NET-default
// operational modes (CompressMetadata without GZip), exercising the uncompressed path.
if (useGZip)
subscriber.CompressionModes |= CompressionModes.GZip;

StatusMessage($"Metadata mode: {(useGZip ? "GZip (compressed)" : "default (uncompressed)")} negotiation.");
subscriber.Initialize();
subscriber.Start();

// Wait for metadata, a deserialization error, or a timeout.
if (!s_metadataComplete.Wait(TimeSpan.FromSeconds(15)))
{
ErrorMessage("METADATA TIMEOUT: no metadata received within 15 seconds.");
s_metadataExitCode = 2;
}

try
{
subscriber.Stop();
}
catch (Exception ex)
{
ErrorMessage($"Error stopping subscriber: {ex.Message}");
}

Environment.Exit(s_metadataExitCode);
}

private static void metadata_ConnectionEstablished(object sender, EventArgs e)
{
StatusMessage("Connection established; requesting metadata...");
s_metadataRequested = true;
(sender as DataSubscriber)?.RefreshMetadata();
}

private static void metadata_ProcessException(object sender, EventArgs<Exception> e)
{
// A malformed xs:dateTime lexical value in the metadata surfaces here: DataSet.ReadXml
// (via DeserializeMetadata) throws and is re-raised as a "Failed to process publisher
// response packet" exception. Any exception after the metadata request is treated as a
// failure of the exchange.
ErrorMessage($"PROCESS EXCEPTION: {e.Argument.Message}");

if (!s_metadataRequested || s_metadataHandled)
return;

s_metadataHandled = true;
s_metadataExitCode = 1;
s_metadataComplete.Set();
}

private static void metadata_MetaDataReceived(object sender, EventArgs<DataSet> e)
{
if (s_metadataHandled)
return;

s_metadataHandled = true;

try
{
DataSet metadata = e.Argument;
StatusMessage($"METADATA RECEIVED: {metadata.Tables.Count} table(s).");

// Persist the received metadata for inspection.
metadata.WriteXml("metadata-received.xml", XmlWriteMode.WriteSchema);
StatusMessage("Wrote metadata-received.xml");

// Dump every DateTime value so the orchestrator can verify the round-trip. Uppercase
// 'F' omits trailing fractional zeros, matching the bare canonical form.
foreach (DataTable table in metadata.Tables)
{
foreach (DataColumn column in table.Columns)
{
if (column.DataType != typeof(DateTime))
continue;

foreach (DataRow row in table.Rows)
{
if (row[column] == DBNull.Value)
continue;

DateTime value = (DateTime)row[column];
StatusMessage($"DATETIME {table.TableName}.{column.ColumnName} = {value:yyyy-MM-ddTHH:mm:ss.FFFFFFF}");
}
}
}

StatusMessage("METADATA OK");
s_metadataExitCode = 0;
}
catch (Exception ex)
{
ErrorMessage($"METADATA FAILURE while inspecting received data: {ex.Message}");
s_metadataExitCode = 1;
}
finally
{
s_metadataComplete.Set();
}
}
}