Skip to content

Commit

Permalink
Various bugfixes for CTT and others (#2212)
Browse files Browse the repository at this point in the history
- SessionClientBatched doesn't handle the case when diagnosticinfos are requested and the batched calls returns mixed empty or non empty diagnostic infos, then the returned infos did not match the number of requested items.
- write of arrays of analogItemState are not properly checked
- BuildInfo properties were empty because BuildInfoVariableValue is never initialized
- DataType and ReferenceType may return null attributes instead of BadAttributeIdInvalid.
- Add BuildInfo test and fix the NodeCacheTest
  • Loading branch information
mregen committed Jul 4, 2023
1 parent 04f51c2 commit cd38eca
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 19 deletions.
20 changes: 20 additions & 0 deletions Libraries/Opc.Ua.Client/SessionClientBatched.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,26 @@ internal set
}
else
{
bool hasDiagnosticInfos = diagnosticInfos.Count > 0;
bool hasEmptyDiagnosticInfos = diagnosticInfos.Count == 0 && results.Count > 0;
bool hasBatchDiagnosticInfos = batchedDiagnosticInfos.Count > 0;
int correctionCount = 0;
if (hasBatchDiagnosticInfos && hasEmptyDiagnosticInfos)
{
correctionCount = results.Count;
}
else if (!hasBatchDiagnosticInfos && hasDiagnosticInfos)
{
correctionCount = batchedResults.Count;
}
if (correctionCount > 0)
{
// fill missing diagnostics infos with empty ones
for (int i = 0; i < correctionCount; i++)
{
diagnosticInfos.Add(new DiagnosticInfo());
}
}
results.AddRange(batchedResults);
diagnosticInfos.AddRange(batchedDiagnosticInfos);
}
Expand Down
37 changes: 30 additions & 7 deletions Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1896,25 +1896,48 @@ protected virtual bool IsReferenceInView(ServerSystemContext context, Continuati
continue;
}

// check if the node is AnalogItem and the value is outside the InstrumentRange.
// check if the node is AnalogItem and the values are outside the InstrumentRange.
AnalogItemState analogItemState = handle.Node as AnalogItemState;
if (analogItemState != null && analogItemState.InstrumentRange != null)
{
try
{
double newValue = System.Convert.ToDouble(nodeToWrite.Value.Value);

if (newValue > analogItemState.InstrumentRange.Value.High ||
newValue < analogItemState.InstrumentRange.Value.Low)
if (nodeToWrite.Value.Value is Array array)
{
errors[ii] = StatusCodes.BadOutOfRange;
continue;
bool isOutOfRange = false;
foreach (var arrayValue in array)
{
double newValue = Convert.ToDouble(arrayValue);
if (newValue > analogItemState.InstrumentRange.Value.High ||
newValue < analogItemState.InstrumentRange.Value.Low)
{
isOutOfRange = true;
break;
}
}
if (isOutOfRange)
{
errors[ii] = StatusCodes.BadOutOfRange;
continue;
}
}
else
{
double newValue = Convert.ToDouble(nodeToWrite.Value.Value);

if (newValue > analogItemState.InstrumentRange.Value.High ||
newValue < analogItemState.InstrumentRange.Value.Low)
{
errors[ii] = StatusCodes.BadOutOfRange;
continue;
}
}
}
catch
{
//skip the InstrumentRange check if the transformation isn't possible.
}

}

#if DEBUG
Expand Down
26 changes: 24 additions & 2 deletions Libraries/Opc.Ua.Server/Diagnostics/DiagnosticsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,31 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context

if (passiveNode == null)
{
MethodState passiveMethod = predefinedNode as MethodState;
BaseVariableState passiveVariable = predefinedNode as BaseVariableState;
if (passiveVariable != null)
{
if (passiveVariable.NodeId == VariableIds.ServerStatusType_BuildInfo)
{
if (passiveVariable is BuildInfoVariableState)
{
return predefinedNode;
}

BuildInfoVariableState activeNode = new BuildInfoVariableState(passiveVariable.Parent);
activeNode.Create(context, passiveVariable);

// replace the node in the parent.
if (passiveVariable.Parent != null)
{
passiveVariable.Parent.ReplaceChild(context, activeNode);
}

return activeNode;
}
return predefinedNode;
}

MethodState passiveMethod = predefinedNode as MethodState;
if (passiveMethod == null)
{
return predefinedNode;
Expand Down Expand Up @@ -441,7 +464,6 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context
return activeNode;
}


return predefinedNode;
}

Expand Down
18 changes: 12 additions & 6 deletions Libraries/Opc.Ua.Server/Server/ServerInternalData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,12 +655,18 @@ private void CreateServerObject()
CurrentTime = DateTime.UtcNow,
State = ServerState.Shutdown
};
serverStatus.BuildInfo.ProductName = m_serverDescription.ProductName;
serverStatus.BuildInfo.ProductUri = m_serverDescription.ProductUri;
serverStatus.BuildInfo.ManufacturerName = m_serverDescription.ManufacturerName;
serverStatus.BuildInfo.SoftwareVersion = m_serverDescription.SoftwareVersion;
serverStatus.BuildInfo.BuildNumber = m_serverDescription.BuildNumber;
serverStatus.BuildInfo.BuildDate = m_serverDescription.BuildDate;

var buildInfo = new BuildInfo() {
ProductName = m_serverDescription.ProductName,
ProductUri = m_serverDescription.ProductUri,
ManufacturerName = m_serverDescription.ManufacturerName,
SoftwareVersion = m_serverDescription.SoftwareVersion,
BuildNumber = m_serverDescription.BuildNumber,
BuildDate = m_serverDescription.BuildDate,
};
var buildInfoVariableState = (BuildInfoVariableState)m_diagnosticsNodeManager.FindPredefinedNode(VariableIds.Server_ServerStatus_BuildInfo, typeof(BuildInfoVariableState));
var buildInfoVariable = new BuildInfoVariableValue(buildInfoVariableState, buildInfo, null);
serverStatus.BuildInfo = buildInfoVariable.Value;

serverObject.ServerStatus.MinimumSamplingInterval = 1000;
serverObject.ServerStatus.CurrentTime.MinimumSamplingInterval = 1000;
Expand Down
5 changes: 5 additions & 0 deletions Stack/Opc.Ua.Core/Stack/State/DataTypeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ public override void Update(ISystemContext context, BinaryDecoder decoder, Attri
value = dataTypeDefinition;
}

if (value == null && result == null)
{
return StatusCodes.BadAttributeIdInvalid;
}

return result;
}
}
Expand Down
9 changes: 8 additions & 1 deletion Stack/Opc.Ua.Core/Stack/State/ReferenceTypeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,14 @@ public override void Update(ISystemContext context, BinaryDecoder decoder, Attri

if (ServiceResult.IsGood(result))
{
value = inverseName;
if (inverseName == null)
{
result = StatusCodes.BadAttributeIdInvalid;
}
else
{
value = inverseName;
}
}

return result;
Expand Down
42 changes: 42 additions & 0 deletions Tests/Opc.Ua.Client.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,48 @@ public async Task TransferSubscriptionNative(bool sendInitialData)
transferSession?.Dispose();
}
}

/// <summary>
/// Read BuildInfo and ensure the values in the structure are the same as in the properties.
/// </summary>
[Test, Order(10000)]
public void ReadBuildInfo()
{
NodeIdCollection nodes = new NodeIdCollection()
{
VariableIds.Server_ServerStatus_BuildInfo,
VariableIds.Server_ServerStatus_BuildInfo_ProductName,
VariableIds.Server_ServerStatus_BuildInfo_ProductUri,
VariableIds.Server_ServerStatus_BuildInfo_ManufacturerName,
VariableIds.Server_ServerStatus_BuildInfo_SoftwareVersion,
VariableIds.Server_ServerStatus_BuildInfo_BuildNumber,
VariableIds.Server_ServerStatus_BuildInfo_BuildDate
};

Session.ReadNodes(nodes, out IList<Node> nodeCollection, out IList<ServiceResult> errors);
Assert.NotNull(nodeCollection);
Assert.NotNull(errors);
Assert.AreEqual(nodes.Count, nodeCollection.Count);
Assert.AreEqual(nodes.Count, errors.Count);

Session.ReadValues(nodes, out DataValueCollection values, out IList<ServiceResult> errors2);
Assert.NotNull(values);
Assert.NotNull(errors);
Assert.AreEqual(nodes.Count, values.Count);
Assert.AreEqual(nodes.Count, errors2.Count);

IList<VariableNode> variableNodes = nodeCollection.Cast<VariableNode>().ToList();

// test build info contains the equal values as the properties
var buildInfo = (values[0].Value as ExtensionObject)?.Body as BuildInfo;
Assert.NotNull(buildInfo);
Assert.AreEqual(buildInfo.ProductName, values[1].Value);
Assert.AreEqual(buildInfo.ProductUri, values[2].Value);
Assert.AreEqual(buildInfo.ManufacturerName, values[3].Value);
Assert.AreEqual(buildInfo.SoftwareVersion, values[4].Value);
Assert.AreEqual(buildInfo.BuildNumber, values[5].Value);
Assert.AreEqual(buildInfo.BuildDate, values[6].Value);
}
#endregion

#region Benchmarks
Expand Down
8 changes: 5 additions & 3 deletions Tests/Opc.Ua.Client.Tests/NodeCacheTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,11 @@ public void NodeCacheTestAllMethodsConcurrently()
}

Random random = new Random(62541);
var testSet1 = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
var testSet2 = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
var testSet3 = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
var testSetAll = ReferenceDescriptions.OrderBy(o => random.Next()).Where(r=>r.NodeClass == NodeClass.Variable).Select(r => r.NodeId).ToList();
var testSet1 = testSetAll.Take(kTestSetSize).ToList();
var testSet2 = testSetAll.Skip(kTestSetSize).Take(kTestSetSize).ToList();
var testSet3 = testSetAll.Skip(kTestSetSize * 2).Take(kTestSetSize).ToList();

var taskList = new List<Task>();
var refTypeIds = new List<NodeId>() { ReferenceTypeIds.HierarchicalReferences };

Expand Down

0 comments on commit cd38eca

Please sign in to comment.