Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
ec27fd0
Initial shared code implementation
ritchiecarroll Apr 26, 2025
86b3310
Added warning suppressions
ritchiecarroll Apr 26, 2025
d0ccb19
Update to .NET 9.0 / System.Threading.Lock
ritchiecarroll Apr 28, 2025
759215c
Cleaned up code formatting
ritchiecarroll Apr 30, 2025
d5f126e
Added simple setting for UDP data channel support
ritchiecarroll Apr 30, 2025
2334fc2
Removed NodeID from device metadata in Gemstone build
ritchiecarroll Apr 30, 2025
a5a1325
Added initial different schema accommodations
ritchiecarroll May 1, 2025
5052970
change allowedRoles to allowedResouces and fix typing issues
prestoncraw May 6, 2025
2693d56
Updated data publisher for new schema and update SQL syntax for TOP c…
ritchiecarroll May 23, 2025
23d4146
Updated cross-platform compile code
ritchiecarroll May 23, 2025
858bb76
Updated import certificate to have special rights
ritchiecarroll May 23, 2025
62766b1
add Settings UI for STTP
prestoncraw May 30, 2025
161c84f
update settings ui
prestoncraw Jun 2, 2025
e1e945a
update ui
prestoncraw Jun 6, 2025
3c4b89f
update ui
prestoncraw Jun 9, 2025
29a1d09
add label attributes to conParams missing them
prestoncraw Jun 24, 2025
dd3ccb2
update UI
prestoncraw Jul 7, 2025
dda6216
update UI
prestoncraw Jul 7, 2025
57eb94e
update UI
prestoncraw Jul 9, 2025
a65ded3
added labels to adapter commands
prestoncraw Jul 10, 2025
d732f38
add parameter attributes to adapter commands
prestoncraw Jul 10, 2025
38621cb
update UI
prestoncraw Jul 15, 2025
f51f2da
Updated NuGet package versions to 1.0.135
ritchiecarroll Aug 16, 2025
aaaddc6
Updated packages to v1.0.136
ritchiecarroll Aug 20, 2025
81d62b8
Updated sttp.gsf project to build properly even when realive to openH…
ritchiecarroll Aug 22, 2025
de9f48a
Cleaned up DataPublisher nullable warnings
ritchiecarroll Aug 22, 2025
b21f170
Updated event callers to be virtual
ritchiecarroll Aug 24, 2025
148864d
Fixed proper length calculation offset in subscriber server commands
ritchiecarroll Aug 26, 2025
0e3651b
Remove access levels from adapter commands
StephenCWills Aug 21, 2025
f670d8c
Merge pull request #9 from sttp/authentication-providers
clackner-gpa Sep 2, 2025
08cb13c
update UI
prestoncraw Sep 3, 2025
c11009c
Moved length adjustment to user-defined command handling only
ritchiecarroll Sep 4, 2025
2fab223
Updated GSF sync to optionally handle frames per second field
ritchiecarroll Sep 4, 2025
88b801d
Merge pull request #10 from sttp/development
ritchiecarroll Sep 4, 2025
6258552
Updated NuGet packages to v1.0.139
ritchiecarroll Sep 4, 2025
0a32587
move UI protocol to derived Adapter class and fix usage of AdapterPro…
prestoncraw Sep 8, 2025
aed8e1a
Updated phasor primary voltage ID compatibility data source sync
ritchiecarroll Sep 9, 2025
98e8828
update ui
prestoncraw Sep 10, 2025
3a04c84
Improved transaction detection
ritchiecarroll Sep 11, 2025
5fb1f24
Updated data subscriber to assign default output measurements when fi…
ritchiecarroll Sep 11, 2025
acacb02
Added internal device flag updates to metadata sync ops
ritchiecarroll Sep 11, 2025
5764f86
Updated Gemstone package references to v1.0.140
ritchiecarroll Sep 11, 2025
c8f77e2
Updated Gemstone Phasor sync to include Internal field
ritchiecarroll Sep 12, 2025
1b59162
update UI
prestoncraw Sep 15, 2025
1d17084
Updated Gemstone release package references to v1.0.141
ritchiecarroll Sep 16, 2025
55ad299
Improved Guid conversion suring metadata sync
ritchiecarroll Sep 17, 2025
da4508a
Improved phaosr metadata field names for Gemstone
ritchiecarroll Sep 17, 2025
fc1b0ff
update UI
prestoncraw Sep 20, 2025
c69c076
ui update
prestoncraw Sep 23, 2025
9433b20
Updated Gemstone references to 1.0.142
ritchiecarroll Sep 24, 2025
f7f14b9
update UI
prestoncraw Oct 2, 2025
a5ede33
Updated Gemstone references to v1.0.44
ritchiecarroll Oct 3, 2025
06323ef
Updated Gemstone packages to v1.0.146
ritchiecarroll Oct 6, 2025
34aa5b9
Simplified configuration platforms
ritchiecarroll Oct 7, 2025
ab067d6
update UI
prestoncraw Oct 17, 2025
433699c
Updated Gemstone library references to version 1.0.148
ritchiecarroll Oct 24, 2025
ec83757
update UI
prestoncraw Nov 6, 2025
80eaaba
Merge pull request #11 from sttp/adapterUI-Test
clackner-gpa Nov 7, 2025
340aa6e
Updated Gemstone library version to 1.0.149
ritchiecarroll Oct 27, 2025
c240dba
update UI
prestoncraw Nov 14, 2025
9d48e99
Updated Gemstone package references to version 1.0.152
clackner-gpa Nov 16, 2025
a484606
Updated dependencies
clackner-gpa Nov 20, 2025
e88d152
update dependencies
clackner-gpa Nov 27, 2025
fbd7218
Updated Dependencies
clackner-gpa Dec 2, 2025
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
14 changes: 10 additions & 4 deletions src/lib/Common.cs → src/lib/sttp.core/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
//
//******************************************************************************************************

using System.Security.Cryptography;
using GSF.Threading;
#if NET
#define MONO
#endif

#if !MONO
using Microsoft.Win32;
Expand All @@ -40,13 +41,13 @@ public static class Common
static Common()
{
#if MONO
UseManagedEncryption = true;
UseManagedEncryption = true;
#else
const string FipsKeyOld = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa";
const string FipsKeyNew = @"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy";

// Determine if the operating system configuration to set to use FIPS-compliant algorithms
UseManagedEncryption = (Registry.GetValue(FipsKeyNew, "Enabled", 0) ?? Registry.GetValue(FipsKeyOld, "FipsAlgorithmPolicy", 0)).ToString() == "0";
UseManagedEncryption = ((Registry.GetValue(FipsKeyNew, "Enabled", 0) ?? Registry.GetValue(FipsKeyOld, "FipsAlgorithmPolicy", 0))?.ToString() ?? "0") == "0";
#endif

TimerScheduler = new SharedTimerScheduler();
Expand All @@ -64,7 +65,12 @@ public static SymmetricAlgorithm SymmetricAlgorithm
{
get
{
#if NET
Aes symmetricAlgorithm = Aes.Create();
#else
Aes symmetricAlgorithm = UseManagedEncryption ? new AesManaged() : new AesCryptoServiceProvider();
#endif

symmetricAlgorithm.KeySize = 256;
return symmetricAlgorithm;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@
//
//******************************************************************************************************

using System;
using GSF;
using GSF.Parsing;
using GSF.TimeSeries;
using GSF.TimeSeries.Transport;

namespace sttp;

#region [ Enumerations ]
Expand Down Expand Up @@ -195,7 +189,7 @@ public class CompactMeasurement : Measurement, IBinaryMeasurement
/// <param name="baseTimeOffsets">Base time offset array - set to <c>null</c> to use full fidelity measurement time.</param>
/// <param name="timeIndex">Time index to use for base offset.</param>
/// <param name="useMillisecondResolution">Flag that determines if millisecond resolution is in use for this serialization.</param>
public CompactMeasurement(SignalIndexCache signalIndexCache, bool includeTime = true, long[] baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)
public CompactMeasurement(SignalIndexCache signalIndexCache, bool includeTime = true, long[]? baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)
{
m_signalIndexCache = signalIndexCache;
IncludeTime = includeTime;
Expand All @@ -215,7 +209,7 @@ public CompactMeasurement(SignalIndexCache signalIndexCache, bool includeTime =
/// <param name="baseTimeOffsets">Base time offset array - set to <c>null</c> to use full fidelity measurement time.</param>
/// <param name="timeIndex">Time index to use for base offset.</param>
/// <param name="useMillisecondResolution">Flag that determines if millisecond resolution is in use for this serialization.</param>
public CompactMeasurement(IMeasurement measurement, SignalIndexCache signalIndexCache, bool includeTime = true, long[] baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)
public CompactMeasurement(IMeasurement measurement, SignalIndexCache signalIndexCache, bool includeTime = true, long[]? baseTimeOffsets = null, int timeIndex = 0, bool useMillisecondResolution = false)
{
Metadata = measurement.Metadata;
Value = measurement.Value;
Expand Down Expand Up @@ -335,7 +329,7 @@ public int RuntimeID
set
{
// Attempt to restore signal identification
if (m_signalIndexCache.Reference.TryGetValue(value, out MeasurementKey key))
if (m_signalIndexCache.Reference.TryGetValue(value, out MeasurementKey? key) /* && key is not null */)
Metadata = key.Metadata;
else
throw new InvalidOperationException($"Failed to find associated signal identification for runtime ID {value}");
Expand Down
112 changes: 52 additions & 60 deletions src/lib/DataGapRecoverer.cs → src/lib/sttp.core/DataGapRecoverer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,11 @@
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using GSF;
using GSF.Diagnostics;
using GSF.IO;
using GSF.Threading;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
using GSF.Units;
#if NET
using CommonFunc = Gemstone.Common;
#else
using CommonFunc = GSF.Common;
#endif

namespace sttp;

Expand Down Expand Up @@ -110,42 +100,42 @@ public class DataGapRecoverer : ISupportLifecycle, IProvideStatus
/// <remarks>
/// <see cref="EventArgs{T}.Argument"/> is a collection of measurements for consumer to process.
/// </remarks>
public event EventHandler<EventArgs<ICollection<IMeasurement>>> RecoveredMeasurements;
public event EventHandler<EventArgs<ICollection<IMeasurement>>>? RecoveredMeasurements;

/// <summary>
/// Provides status messages to consumer.
/// </summary>
/// <remarks>
/// <see cref="EventArgs{T}.Argument"/> is new status message.
/// </remarks>
public event EventHandler<EventArgs<string>> StatusMessage;
public event EventHandler<EventArgs<string>>? StatusMessage;

/// <summary>
/// Event is raised when there is an exception encountered during <see cref="DataGapRecoverer"/> processing.
/// </summary>
/// <remarks>
/// <see cref="EventArgs{T}.Argument"/> is the exception that was thrown.
/// </remarks>
public event EventHandler<EventArgs<Exception>> ProcessException;
public event EventHandler<EventArgs<Exception>>? ProcessException;

/// <summary>
/// Raised after the <see cref="DataGapRecoverer"/> has been properly disposed.
/// </summary>
public event EventHandler Disposed;
public event EventHandler? Disposed;

// Fields
private readonly SubscriptionInfo m_subscriptionInfo;
private readonly ManualResetEventSlim m_dataGapRecoveryCompleted;
private DataSubscriber m_temporalSubscription;
private SharedTimer m_dataStreamMonitor;
private DataSet m_dataSource;
private string m_loggingPath;
private string m_sourceConnectionName;
private string m_connectionString;
private readonly SharedTimer m_dataStreamMonitor;
private DataSubscriber? m_temporalSubscription;
private DataSet? m_dataSource;
private string m_loggingPath = string.Empty;
private string m_sourceConnectionName = default!;
private string m_connectionString = default!;
private Time m_recoveryStartDelay;
private Time m_minimumRecoverySpan;
private Time m_maximumRecoverySpan;
private Outage m_currentDataGap;
private Outage? m_currentDataGap;
private Ticks m_mostRecentRecoveredTime;
private long m_measurementsRecoveredForDataGap;
private long m_measurementsRecoveredOverLastInterval;
Expand Down Expand Up @@ -230,7 +220,7 @@ public string SourceConnectionName
/// <summary>
/// Gets or sets <see cref="DataSet"/> based data source available to this <see cref="DataGapRecoverer"/>.
/// </summary>
public DataSet DataSource
public DataSet? DataSource
{
get => m_dataSource;
set
Expand Down Expand Up @@ -437,7 +427,7 @@ public bool UseMillisecondResolution
/// Gets or sets any additional constraint parameters that will be supplied to adapters in temporal
/// subscription used when recovering data for an <see cref="Outage"/>.
/// </summary>
public string ConstraintParameters
public string? ConstraintParameters
{
get => m_subscriptionInfo.ConstraintParameters;
set => m_subscriptionInfo.ConstraintParameters = value;
Expand Down Expand Up @@ -469,12 +459,12 @@ public bool Enabled
/// <summary>
/// Gets reference to the data gap <see cref="OutageLog"/> for this <see cref="DataGapRecoverer"/>.
/// </summary>
protected OutageLog DataGapLog { get; private set; }
protected OutageLog? DataGapLog { get; private set; }

/// <summary>
/// Gets reference to the data gap <see cref="OutageLogProcessor"/> for this <see cref="DataGapRecoverer"/>.
/// </summary>
protected OutageLogProcessor DataGapLogProcessor { get; private set; }
protected OutageLogProcessor? DataGapLogProcessor { get; private set; }

// Gets the name of the data gap recoverer.
string IProvideStatus.Name => m_temporalSubscription is null ? GetType().Name : m_temporalSubscription.Name;
Expand Down Expand Up @@ -505,7 +495,7 @@ public string Status
status.AppendLine($" Logging path: {FilePath.TrimFileName(m_loggingPath.ToNonNullNorWhiteSpace(FilePath.GetAbsolutePath("")), 51)}");
status.AppendLine($"Last recovered measurement: {((DateTime)m_mostRecentRecoveredTime).ToString(OutageLog.DateTimeFormat)}");

Outage currentDataGap = m_currentDataGap;
Outage? currentDataGap = m_currentDataGap;

if (currentDataGap is not null)
status.AppendLine($" Currently recovering: {currentDataGap.Start.ToString(OutageLog.DateTimeFormat)} to {currentDataGap.End.ToString(OutageLog.DateTimeFormat)}");
Expand Down Expand Up @@ -557,20 +547,13 @@ protected virtual void Dispose(bool disposing)
if (!disposing)
return;

if (m_dataGapRecoveryCompleted is not null)
{
// Signal any waiting threads
m_abnormalTermination = true;
m_dataGapRecoveryCompleted.Set();
m_dataGapRecoveryCompleted.Dispose();
}
// Signal any waiting threads
m_abnormalTermination = true;
m_dataGapRecoveryCompleted.Set();
m_dataGapRecoveryCompleted.Dispose();

if (m_dataStreamMonitor is not null)
{
m_dataStreamMonitor.Elapsed -= DataStreamMonitor_Elapsed;
m_dataStreamMonitor.Dispose();
m_dataStreamMonitor = null;
}
m_dataStreamMonitor.Elapsed -= DataStreamMonitor_Elapsed;
m_dataStreamMonitor.Dispose();

if (DataGapLogProcessor is not null)
{
Expand Down Expand Up @@ -613,7 +596,7 @@ public void Initialize()

Dictionary<string, string> settings = m_connectionString.ToNonNullString().ParseKeyValuePairs();

if (settings.TryGetValue("sourceConnectionName", out string setting) && !string.IsNullOrWhiteSpace(setting))
if (settings.TryGetValue("sourceConnectionName", out string? setting) && !string.IsNullOrWhiteSpace(setting))
m_sourceConnectionName = setting;

if (settings.TryGetValue("recoveryStartDelay", out setting) && double.TryParse(setting, out double timeInterval))
Expand Down Expand Up @@ -683,7 +666,7 @@ public void Initialize()
DataGapLog.Initialize();

// Setup data gap processor to process items one at a time, a 5-second minimum period is established between each gap processing
DataGapLogProcessor = new OutageLogProcessor(DataGapLog, ProcessDataGap, CanProcessDataGap, ex => OnProcessException(MessageLevel.Warning, ex), GSF.Common.Max(5000, (int)(m_recoveryStartDelay * 1000.0D)));
DataGapLogProcessor = new OutageLogProcessor(DataGapLog, ProcessDataGap, CanProcessDataGap, ex => OnProcessException(MessageLevel.Warning, ex), CommonFunc.Max(5000, (int)(m_recoveryStartDelay * 1000.0D)));
}

/// <summary>
Expand Down Expand Up @@ -760,6 +743,9 @@ public bool RemoveDataGap(DateTimeOffset startTime, DateTimeOffset endTime)
/// <returns>The contents of the outage log.</returns>
public string DumpOutageLog()
{
if (DataGapLog is null)
throw new InvalidOperationException("Data gap log is not defined -- cannot dump outage log.");

List<Outage> outages = DataGapLog.Outages;
StringBuilder dump = new();

Expand All @@ -781,6 +767,12 @@ private bool CanProcessDataGap(Outage dataGap)
// to requeue the data gap outage so it will be processed again (could be that remote system is offline).
private void ProcessDataGap(Outage dataGap)
{
if (m_temporalSubscription is null)
throw new InvalidOperationException("Temporal subscription is not established -- cannot process data gap.");

if (DataGapLog is null)
throw new InvalidOperationException("Data gap log is not defined -- cannot process data gap.");

// Establish start and stop time for temporal session
m_subscriptionInfo.StartTime = dataGap.Start.ToString(OutageLog.DateTimeFormat, CultureInfo.InvariantCulture);
m_subscriptionInfo.StopTime = dataGap.End.ToString(OutageLog.DateTimeFormat, CultureInfo.InvariantCulture);
Expand Down Expand Up @@ -817,7 +809,7 @@ private void ProcessDataGap(Outage dataGap)
if (m_abnormalTermination)
{
// Make sure any data recovered so far doesn't get unnecessarily re-recovered, this requires that source historian report data in time-sorted order
dataGap = new Outage(new DateTime(GSF.Common.Max((Ticks)dataGap.Start.Ticks, m_mostRecentRecoveredTime - (m_subscriptionInfo.UseMillisecondResolution ? Ticks.PerMillisecond : 1L)), DateTimeKind.Utc), dataGap.End);
dataGap = new Outage(new DateTime(CommonFunc.Max((Ticks)dataGap.Start.Ticks, m_mostRecentRecoveredTime - (m_subscriptionInfo.UseMillisecondResolution ? Ticks.PerMillisecond : 1L)), DateTimeKind.Utc), dataGap.End);

// Re-insert adjusted data gap at the top of the processing queue
DataGapLog.Add(dataGap);
Expand All @@ -843,7 +835,7 @@ protected virtual void OnRecoveredMeasurements(ICollection<IMeasurement> measure
{
try
{
RecoveredMeasurements?.Invoke(this, new EventArgs<ICollection<IMeasurement>>(measurements));
RecoveredMeasurements?.SafeInvoke(this, new EventArgs<ICollection<IMeasurement>>(measurements));
}
catch (Exception ex)
{
Expand All @@ -864,14 +856,14 @@ protected virtual void OnRecoveredMeasurements(ICollection<IMeasurement> measure
/// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this
/// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred.
/// </remarks>
protected virtual void OnStatusMessage(MessageLevel level, string status, string eventName = null, MessageFlags flags = MessageFlags.None)
protected virtual void OnStatusMessage(MessageLevel level, string status, string? eventName = null, MessageFlags flags = MessageFlags.None)
{
try
{
Log.Publish(level, flags, eventName ?? "DataGapRecovery", status);

using (Logger.SuppressLogMessages())
StatusMessage?.Invoke(this, new EventArgs<string>(AdapterBase.GetStatusWithMessageLevelPrefix(status, level)));
StatusMessage?.SafeInvoke(this, new EventArgs<string>(AdapterBase.GetStatusWithMessageLevelPrefix(status, level)));
}
catch (Exception ex)
{
Expand All @@ -892,14 +884,14 @@ protected virtual void OnStatusMessage(MessageLevel level, string status, string
/// generated. In general, there should only be a few dozen distinct event names per class. Exceeding this
/// threshold will cause the EventName to be replaced with a general warning that a usage issue has occurred.
/// </remarks>
protected virtual void OnProcessException(MessageLevel level, Exception exception, string eventName = null, MessageFlags flags = MessageFlags.None)
protected virtual void OnProcessException(MessageLevel level, Exception exception, string? eventName = null, MessageFlags flags = MessageFlags.None)
{
try
{
Log.Publish(level, flags, eventName ?? "DataGapRecovery", exception?.Message, null, exception);
Log.Publish(level, flags, eventName ?? "DataGapRecovery", exception.Message, null, exception);

using (Logger.SuppressLogMessages())
ProcessException?.Invoke(this, new EventArgs<Exception>(exception));
ProcessException?.SafeInvoke(this, new EventArgs<Exception>(exception));
}
catch (Exception ex)
{
Expand All @@ -913,12 +905,12 @@ private string GetLoggingPath(string filePath)
return string.IsNullOrWhiteSpace(m_loggingPath) ? FilePath.GetAbsolutePath(filePath) : Path.Combine(m_loggingPath, filePath);
}

private void TemporalSubscription_ConnectionEstablished(object sender, EventArgs e)
private void TemporalSubscription_ConnectionEstablished(object? sender, EventArgs e)
{
m_connected = true;
}

private void TemporalSubscription_ConnectionTerminated(object sender, EventArgs e)
private void TemporalSubscription_ConnectionTerminated(object? sender, EventArgs e)
{
m_connected = false;

Expand All @@ -937,15 +929,15 @@ private void TemporalSubscription_ConnectionTerminated(object sender, EventArgs
}
}

private void TemporalSubscription_ProcessingComplete(object sender, EventArgs<string> e)
private void TemporalSubscription_ProcessingComplete(object? sender, EventArgs<string> e)
{
OnStatusMessage(MessageLevel.Info, "Temporal data recovery processing completed.");

m_dataGapRecoveryCompleted.Set();
m_dataStreamMonitor.Enabled = false;
}

private void TemporalSubscription_NewMeasurements(object sender, EventArgs<ICollection<IMeasurement>> e)
private void TemporalSubscription_NewMeasurements(object? sender, EventArgs<ICollection<IMeasurement>> e)
{
ICollection<IMeasurement> measurements = e.Argument;
int total = measurements.Count;
Expand Down Expand Up @@ -976,17 +968,17 @@ private void TemporalSubscription_NewMeasurements(object sender, EventArgs<IColl
m_dataStreamMonitor.Enabled = false;
}

private void Common_StatusMessage(object sender, EventArgs<string> e)
private void Common_StatusMessage(object? sender, EventArgs<string> e)
{
OnStatusMessage(MessageLevel.Info, e.Argument);
}

private void Common_ProcessException(object sender, EventArgs<Exception> e)
private void Common_ProcessException(object? sender, EventArgs<Exception> e)
{
OnProcessException(MessageLevel.Warning, e.Argument);
}

private void DataStreamMonitor_Elapsed(object sender, EventArgs<DateTime> e)
private void DataStreamMonitor_Elapsed(object? sender, EventArgs<DateTime> e)
{
if (m_measurementsRecoveredOverLastInterval == 0)
{
Expand Down
Loading