OPCFoundation / UA-.NETStandard

OPC Unified Architecture .NET Standard
Other
1.96k stars 946 forks source link

Duplicate references after export via NodeStateCollection.SaveAsNodeSet2 #1821

Open s-geiger-si opened 2 years ago

s-geiger-si commented 2 years ago

Type of issue

Current Behavior

We are calling NodeStateCollection::SaveAsNodeSet2 to save a NodeStateCollection to XML. After the file has been saved to disk we see that all HasSubtypeReferences reference appear exactly twice in the saved XML file.

The cause seems to be that the NodeStateCollection has an object type which has an associated super type and many subtypes. Specifically, the SuperTypeId field is set to a super type and the list of references contains sub types.

Expected Behavior

When the file is saved each exported reference should occur only one time in the XML file. When processing references there should be some check in place to skip references which have already been added.

Steps To Reproduce

We have traced the bug to the BaseTypeState.PopulateBrowser method as can be seen in this stack trace: image

What appears to happen is the following:

  1. Starting from NodeStateCollection:SaveAsNodeSet2 we call into BaseTypeState.Export, which delegates to the base implementation in UaNodeSet::Export.
  2. In UaNodeSet::Export we create an INodeBrowser via a call to node.CreateBrowser. The node is in our case derived from ObjectType:
    INodeBrowser browser = node.CreateBrowser(context, null, null, true, BrowseDirection.Both, null, null, true);
    List<Reference> exportedReferences = new List<Reference>();

    Note that the direction is set to BrowseDirection.Both and the includeSubtypes flag is set to true.

  3. In NodeState::CreateBrowser we call the virtual PopulateBrowser() method which is overridden in BaseTypeState::PopulateBrowser: https://github.com/OPCFoundation/UA-.NETStandard/blob/eda38cb6c22d6e7c4cb2e5507cb76f14849215cd/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs#L384
  4. This calls base.PopulateBrowser which is implemented in NodeState::PopulateBrowser. Here we first add all references of the node: https://github.com/OPCFoundation/UA-.NETStandard/blob/eda38cb6c22d6e7c4cb2e5507cb76f14849215cd/Stack/Opc.Ua.Core/Stack/State/NodeState.cs#L3133
  5. However, in the overriden PopulateBrowser methods we then find all subtypes and add them again: https://github.com/OPCFoundation/UA-.NETStandard/blob/eda38cb6c22d6e7c4cb2e5507cb76f14849215cd/Stack/Opc.Ua.Core/Stack/State/BaseTypeState.cs#L405

    protected override void PopulateBrowser(ISystemContext context, NodeBrowser browser)
    {
       base.PopulateBrowser(context, browser);   // (a) this adds all references
    
       [...]
    
       // use the type table to find the subtypes.
       if (context.TypeTable != null && this.NodeId != null)
       {
           if (browser.IsRequired(ReferenceTypeIds.HasSubtype, false))
           {
               IList<NodeId> subtypeIds = context.TypeTable.FindSubTypes(this.NodeId);
    
               for (int ii = 0; ii < subtypeIds.Count; ii++)
               {
                   // (b) this adds subtype references which were already added in step (a) above
                   browser.Add(ReferenceTypeIds.HasSubtype, false, subtypeIds[ii]);
               }
           }
       }
    }

We have only seen this for NodeStateCollections which contain object types that again have subtype references.

Environment

- OS: Linux (based on the code analysis above, this should not be a platform specific issue)
- Environment: not an environment specific issue
- Runtime: Dotnet 6.0.x
- Nuget Version: We are using 1.4.365.48, but since I can trace the same code path in the latest master branch it should not matter.
- Component: Opc.Ua.Core
- Server: Derived from StandardServer
- Client: N/A

Anything else?

No response

s-geiger-si commented 2 years ago

Do you need any additional information? Is there anything else we can provide on this issue?